summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/action.php71
-rw-r--r--lib/attachmentlist.php7
-rw-r--r--lib/common.php12
-rw-r--r--lib/connectsettingsaction.php30
-rw-r--r--lib/daemon.php11
-rw-r--r--lib/dbqueuemanager.php14
-rw-r--r--lib/designsettings.php17
-rw-r--r--lib/facebookaction.php36
-rw-r--r--lib/groupsbymemberssection.php2
-rw-r--r--lib/groupsbypostssection.php2
-rw-r--r--lib/grouptagcloudsection.php2
-rw-r--r--lib/htmloutputter.php55
-rw-r--r--lib/jsonsearchresultslist.php4
-rw-r--r--lib/language.php2
-rw-r--r--lib/logingroupnav.php12
-rw-r--r--lib/mail.php57
-rw-r--r--lib/messageform.php6
-rw-r--r--lib/noticelist.php49
-rw-r--r--lib/oauthclient.php225
-rw-r--r--lib/parallelizingdaemon.php229
-rw-r--r--lib/popularnoticesection.php6
-rw-r--r--lib/profilesection.php2
-rw-r--r--lib/queuehandler.php11
-rw-r--r--lib/router.php42
-rw-r--r--lib/rssaction.php85
-rw-r--r--lib/twitter.php378
-rw-r--r--lib/twitterapi.php219
-rw-r--r--lib/twitteroauthclient.php220
-rw-r--r--lib/util.php20
29 files changed, 1287 insertions, 539 deletions
diff --git a/lib/action.php b/lib/action.php
index a5244371a..6da9adab5 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -193,21 +193,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));
}
@@ -253,26 +244,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; }');
@@ -423,6 +402,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'));
@@ -434,12 +421,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'),
@@ -452,12 +436,17 @@ 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');
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
index f6a1b59d0..41d03f8e2 100644
--- a/lib/attachmentlist.php
+++ b/lib/attachmentlist.php
@@ -340,7 +340,12 @@ class Attachment extends AttachmentListItem
case 'video':
case 'link':
if (!empty($this->oembed->html)) {
- $this->out->raw($this->oembed->html);
+ require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php';
+ $config = array(
+ 'safe'=>1,
+ 'elements'=>'*+object+embed');
+ $this->out->raw(htmLawed($this->oembed->html,$config));
+ //$this->out->raw($this->oembed->html);
}
break;
diff --git a/lib/common.php b/lib/common.php
index 507a2a281..f26155e70 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
-define('LACONICA_VERSION', '0.8.0');
+define('LACONICA_VERSION', '0.8.1dev');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);
@@ -109,6 +109,7 @@ $config =
'broughtbyurl' => null,
'closed' => false,
'inviteonly' => false,
+ 'openidonly' => false,
'private' => false,
'ssl' => 'never',
'sslserver' => null,
@@ -183,11 +184,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',
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/daemon.php b/lib/daemon.php
index 9d89c63e7..231f5414e 100644
--- a/lib/daemon.php
+++ b/lib/daemon.php
@@ -24,6 +24,7 @@ if (!defined('LACONICA')) {
class Daemon
{
var $daemonize = true;
+ var $_id = 'generic';
function __construct($daemonize = true)
{
@@ -35,6 +36,16 @@ class Daemon
return null;
}
+ function get_id()
+ {
+ return $this->_id;
+ }
+
+ function set_id($id)
+ {
+ $this->_id = $id;
+ }
+
function background()
{
$pid = pcntl_fork();
diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php
index a37a8ffdf..19aa9f3ae 100644
--- a/lib/dbqueuemanager.php
+++ b/lib/dbqueuemanager.php
@@ -22,7 +22,6 @@
* @category QueueManager
* @package Laconica
* @author Evan Prodromou <evan@controlyourself.ca>
- * @author Sarven Capadisli <csarven@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/
@@ -86,9 +85,15 @@ class DBQueueManager extends QueueManager
$start = time();
$result = null;
+ $sleeptime = 1;
+
do {
$qi = Queue_item::top($queue);
- if (!empty($qi)) {
+ if (empty($qi)) {
+ $this->_log(LOG_DEBUG, "No new queue items, sleeping $sleeptime seconds.");
+ sleep($sleeptime);
+ $sleeptime *= 2;
+ } else {
$notice = Notice::staticGet('id', $qi->notice_id);
if (!empty($notice)) {
$result = $notice;
@@ -98,6 +103,7 @@ class DBQueueManager extends QueueManager
$qi->free();
$qi = null;
}
+ $sleeptime = 1;
}
} while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout));
@@ -118,7 +124,7 @@ class DBQueueManager extends QueueManager
} else {
if (empty($qi->claimed)) {
$this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '.
- 'for '.$notice->id.', queue '.$queue);
+ 'for '.$notice->id.', queue '.$queue);
}
$qi->delete();
$qi->free();
@@ -145,7 +151,7 @@ class DBQueueManager extends QueueManager
} else {
if (empty($qi->claimed)) {
$this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '.
- 'for '.$notice->id.', queue '.$queue);
+ 'for '.$notice->id.', queue '.$queue);
} else {
$orig = clone($qi);
$qi->claimed = null;
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/facebookaction.php b/lib/facebookaction.php
index 5be2f2fe6..289e702c6 100644
--- a/lib/facebookaction.php
+++ b/lib/facebookaction.php
@@ -95,34 +95,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');
}
/**
@@ -277,8 +256,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/groupsbymemberssection.php b/lib/groupsbymemberssection.php
index 963e21f15..ad4884bf8 100644
--- a/lib/groupsbymemberssection.php
+++ b/lib/groupsbymemberssection.php
@@ -48,7 +48,7 @@ class GroupsByMembersSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_member '.
'ON user_group.id = group_member.group_id ' .
- 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
+ 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified,user_group.design_id ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;
diff --git a/lib/groupsbypostssection.php b/lib/groupsbypostssection.php
index 325b4033f..dc7925d5e 100644
--- a/lib/groupsbypostssection.php
+++ b/lib/groupsbypostssection.php
@@ -48,7 +48,7 @@ class GroupsByPostsSection extends GroupSection
$qry = 'SELECT user_group.*, count(*) as value ' .
'FROM user_group JOIN group_inbox '.
'ON user_group.id = group_inbox.group_id ' .
- 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified ' .
+ 'GROUP BY user_group.id,user_group.nickname,user_group.fullname,user_group.homepage,user_group.description,user_group.location,user_group.original_logo,user_group.homepage_logo,user_group.stream_logo,user_group.mini_logo,user_group.created,user_group.modified,user_group.design_id ' .
'ORDER BY value DESC ';
$limit = GROUPS_PER_SECTION;
diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php
index 9b7a10f6b..0e0cbdd63 100644
--- a/lib/grouptagcloudsection.php
+++ b/lib/grouptagcloudsection.php
@@ -73,7 +73,7 @@ class GroupTagCloudSection extends TagCloudSection
$quoted = array();
foreach ($names as $name) {
- $quoted[] = "\"$name\"";
+ $quoted[] = "'$name'";
}
$namestring = implode(',', $quoted);
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/language.php b/lib/language.php
index 3ea3dd2aa..9ad2d31bd 100644
--- a/lib/language.php
+++ b/lib/language.php
@@ -53,7 +53,7 @@ function client_prefered_language($httplang)
if (!empty($httplang[2][$i])) {
// if no q default to 1.0
$client_langs[$httplang[2][$i]] =
- ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
+ ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0 - ($i*0.01));
}
if (!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
// if a catchall default 0.01 lower
diff --git a/lib/logingroupnav.php b/lib/logingroupnav.php
index f23985f3a..919fd3db9 100644
--- a/lib/logingroupnav.php
+++ b/lib/logingroupnav.php
@@ -72,11 +72,13 @@ class LoginGroupNav extends Widget
// action => array('prompt', 'title')
$menu = array();
- $menu['login'] = array(_('Login'),
- _('Login with a username and password'));
- if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
- $menu['register'] = array(_('Register'),
- _('Sign up for a new account'));
+ if (!common_config('site','openidonly')) {
+ $menu['login'] = array(_('Login'),
+ _('Login with a username and password'));
+ if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
+ $menu['register'] = array(_('Register'),
+ _('Sign up for a new account'));
+ }
}
$menu['openidlogin'] = array(_('OpenID'),
_('Login or register with OpenID'));
diff --git a/lib/mail.php b/lib/mail.php
index 781a7541b..2e471fd6d 100644
--- a/lib/mail.php
+++ b/lib/mail.php
@@ -121,7 +121,7 @@ function mail_notify_from()
$domain = mail_domain();
- $notifyfrom = common_config('site', 'name') .' <noreply@'.$domain.'>';
+ $notifyfrom = '"'.common_config('site', 'name') .'" <noreply@'.$domain.'>';
}
return $notifyfrom;
@@ -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/messageform.php b/lib/messageform.php
index b8878ec1f..8ea2b36c2 100644
--- a/lib/messageform.php
+++ b/lib/messageform.php
@@ -140,6 +140,12 @@ class MessageForm extends Form
'rows' => 4,
'name' => 'content'),
($this->content) ? $this->content : '');
+ $this->out->elementStart('dl', 'form_note');
+ $this->out->element('dt', null, _('Available characters'));
+ $this->out->element('dd', array('id' => 'notice_text-count'),
+ '140');
+ $this->out->elementEnd('dl');
+
}
/**
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 44726a17b..5429d943f 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -350,26 +350,20 @@ 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('dl', 'timestamp');
- $this->out->element('dt', null, _('Published'));
- $this->out->elementStart('dd', null);
$this->out->elementStart('a', array('rel' => 'bookmark',
+ 'class' => 'timestamp',
'href' => $noticeurl));
$dt = common_date_iso8601($this->notice->created);
$this->out->element('abbr', array('class' => 'published',
'title' => $dt),
common_date_string($this->notice->created));
-
$this->out->elementEnd('a');
- $this->out->elementEnd('dd');
- $this->out->elementEnd('dl');
}
/**
@@ -384,8 +378,8 @@ class NoticeListItem extends Widget
function showNoticeSource()
{
if ($this->notice->source) {
- $this->out->elementStart('dl', 'device');
- $this->out->element('dt', null, _('From'));
+ $this->out->elementStart('span', 'source');
+ $this->out->text(_('from'));
$source_name = _($this->notice->source);
switch ($this->notice->source) {
case 'web':
@@ -394,22 +388,22 @@ class NoticeListItem extends Widget
case 'omb':
case 'system':
case 'api':
- $this->out->element('dd', null, $source_name);
+ $this->out->element('span', 'device', $source_name);
break;
default:
$ns = Notice_source::staticGet($this->notice->source);
if ($ns) {
- $this->out->elementStart('dd', null);
+ $this->out->elementStart('span', 'device');
$this->out->element('a', array('href' => $ns->url,
'rel' => 'external'),
$ns->name);
- $this->out->elementEnd('dd');
+ $this->out->elementEnd('span');
} else {
- $this->out->element('dd', null, $source_name);
+ $this->out->element('span', 'device', $source_name);
}
break;
}
- $this->out->elementEnd('dl');
+ $this->out->elementEnd('span');
}
}
@@ -429,13 +423,9 @@ class NoticeListItem extends Widget
&& $this->notice->conversation != $this->notice->id) {
$convurl = common_local_url('conversation',
array('id' => $this->notice->conversation));
- $this->out->elementStart('dl', 'response');
- $this->out->element('dt', null, _('To'));
- $this->out->elementStart('dd');
- $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id),
+ $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id,
+ 'class' => 'response'),
_('in context'));
- $this->out->elementEnd('dd');
- $this->out->elementEnd('dl');
}
}
@@ -453,17 +443,12 @@ class NoticeListItem extends Widget
if (common_logged_in()) {
$reply_url = common_local_url('newnotice',
array('replyto' => $this->profile->nickname));
-
- $this->out->elementStart('dl', 'notice_reply');
- $this->out->element('dt', null, _('Reply to this notice'));
- $this->out->elementStart('dd');
$this->out->elementStart('a', array('href' => $reply_url,
+ 'class' => 'notice_reply',
'title' => _('Reply to this notice')));
$this->out->text(_('Reply'));
$this->out->element('span', 'notice_id', $this->notice->id);
$this->out->elementEnd('a');
- $this->out->elementEnd('dd');
- $this->out->elementEnd('dl');
}
}
@@ -479,13 +464,9 @@ class NoticeListItem extends Widget
if ($user && $this->notice->profile_id == $user->id) {
$deleteurl = common_local_url('deletenotice',
array('notice' => $this->notice->id));
- $this->out->elementStart('dl', 'notice_delete');
- $this->out->element('dt', null, _('Delete this notice'));
- $this->out->elementStart('dd');
$this->out->element('a', array('href' => $deleteurl,
+ 'class' => 'notice_delete',
'title' => _('Delete this notice')), _('Delete'));
- $this->out->elementEnd('dd');
- $this->out->elementEnd('dl');
}
}
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/popularnoticesection.php b/lib/popularnoticesection.php
index e47c9b385..167a6ff8d 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -74,11 +74,7 @@ class PopularNoticeSection extends NoticeSection
$offset = 0;
$limit = NOTICES_PER_SECTION + 1;
- if (common_config('db', 'type') == 'pgsql') {
- $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
- } else {
- $qry .= ' LIMIT ' . $offset . ', ' . $limit;
- }
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
$notice = Memcached_DataObject::cachedQuery('Notice',
sprintf($qry, common_config('popular', 'dropoff')),
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/queuehandler.php b/lib/queuehandler.php
index c2ff10f32..f11e5bd90 100644
--- a/lib/queuehandler.php
+++ b/lib/queuehandler.php
@@ -29,7 +29,6 @@ define('QUEUE_HANDLER_HIT_IDLE', 0);
class QueueHandler extends Daemon
{
- var $_id = 'generic';
function __construct($id=null, $daemonize=true)
{
@@ -55,16 +54,6 @@ class QueueHandler extends Daemon
return strtolower($this->class_name().'.'.$this->get_id());
}
- function get_id()
- {
- return $this->_id;
- }
-
- function set_id($id)
- {
- $this->_id = $id;
- }
-
function transport()
{
return null;
diff --git a/lib/router.php b/lib/router.php
index e12138637..f03cfcf6d 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,6 +117,16 @@ 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'));
+
// these take a code
foreach (array('register', 'confirmaddress', 'recoverpassword') as $c) {
@@ -206,7 +220,7 @@ class Router
array('tag' => '[a-zA-Z0-9]+'));
$m->connect('tag/:tag',
array('action' => 'tag'),
- array('tag' => '[a-zA-Z0-9]+'));
+ array('tag' => '[\pL\pN_\-\.]{1,64}'));
$m->connect('peopletag/:tag',
array('action' => 'peopletag'),
@@ -394,7 +408,33 @@ class Router
array('action' => 'api',
'apiaction' => 'laconica'));
+ $m->connect('api/laconica/:method',
+ array('action' => 'api',
+ '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/rssaction.php b/lib/rssaction.php
index 3b9f0fb47..0aca96566 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -39,6 +39,7 @@ class Rss10Action extends Action
var $creators = array();
var $limit = DEFAULT_RSS_LIMIT;
var $notices = null;
+ var $tags_already_output = array();
/**
* Constructor
@@ -96,11 +97,48 @@ class Rss10Action extends Action
{
// Parent handling, including cache check
parent::handle($args);
+
+ if (common_config('site', 'private')) {
+ if (!isset($_SERVER['PHP_AUTH_USER'])) {
+
+ # This header makes basic auth go
+ header('WWW-Authenticate: Basic realm="Laconica RSS"');
+
+ # If the user hits cancel -- bam!
+ $this->show_basic_auth_error();
+ return;
+ } else {
+ $nickname = $_SERVER['PHP_AUTH_USER'];
+ $password = $_SERVER['PHP_AUTH_PW'];
+
+ if (!common_check_user($nickname, $password)) {
+ # basic authentication failed
+ list($proxy, $ip) = common_client_ip();
+
+ common_log(LOG_WARNING, "Failed RSS auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
+ $this->show_basic_auth_error();
+ return;
+ }
+ }
+ }
+
// Get the list of notices
$this->notices = $this->getNotices($this->limit);
$this->showRss();
}
+ function show_basic_auth_error()
+ {
+ header('HTTP/1.1 401 Unauthorized');
+ header('Content-Type: application/xml; charset=utf-8');
+ $this->startXML();
+ $this->elementStart('hash');
+ $this->element('error', null, 'Could not authenticate you.');
+ $this->element('request', null, $_SERVER['REQUEST_URI']);
+ $this->elementEnd('hash');
+ $this->endXML();
+ }
+
/**
* Get the notices to output in this stream
*
@@ -188,24 +226,6 @@ class Rss10Action extends Action
}
}
- // XXX: Surely there should be a common function to do this?
- function extract_tags ($string)
- {
- $count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($string), $match);
- if (!count)
- {
- return array();
- }
-
- $rv = array();
- foreach ($match[1] as $tag)
- {
- $rv[] = common_canonical_tag($tag);
- }
-
- return array_unique($rv);
- }
-
function showItem($notice)
{
$profile = Profile::staticGet($notice->profile_id);
@@ -230,6 +250,11 @@ class Rss10Action extends Action
$replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
$this->element('sioc:reply_of', array('rdf:resource' => $replyurl));
}
+ if (!empty($notice->conversation)) {
+ $conversationurl = common_local_url('conversation',
+ array('id' => $notice->conversation));
+ $this->element('sioc:has_discussion', array('rdf:resource' => $conversationurl));
+ }
$attachments = $notice->attachments();
if($attachments){
foreach($attachments as $attachment){
@@ -259,18 +284,28 @@ class Rss10Action extends Action
$this->element('sioc:links_to', array('rdf:resource'=>$attachment->url));
}
}
- $tags = $this->extract_tags($notice->content);
- if (!empty($tags)) {
- foreach ($tags as $tag)
- {
- $tagpage = common_local_url('tag', array('tag' => $tag));
- $tagrss = common_local_url('tagrss', array('tag' => $tag));
+
+ $tag = new Notice_tag();
+ $tag->notice_id = $notice->id;
+ if ($tag->find()) {
+ $entry['tags']=array();
+ while ($tag->fetch()) {
+ $tagpage = common_local_url('tag', array('tag' => $tag->tag));
+
+ if ( in_array($tag, $this->tags_already_output) ) {
+ $this->element('ctag:tagged', array('rdf:resource'=>$tagpage.'#concept'));
+ continue;
+ }
+
+ $tagrss = common_local_url('tagrss', array('tag' => $tag->tag));
$this->elementStart('ctag:tagged');
- $this->elementStart('ctag:Tag', array('rdf:about'=>$tagpage.'#concept', 'ctag:label'=>$tag));
+ $this->elementStart('ctag:Tag', array('rdf:about'=>$tagpage.'#concept', 'ctag:label'=>$tag->tag));
$this->element('foaf:page', array('rdf:resource'=>$tagpage));
$this->element('rdfs:seeAlso', array('rdf:resource'=>$tagrss));
$this->elementEnd('ctag:Tag');
$this->elementEnd('ctag:tagged');
+
+ $this->tags_already_output[] = $tag->tag;
}
}
$this->elementEnd('item');
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 ab6c0d62c..583007208 100644
--- a/lib/twitterapi.php
+++ b/lib/twitterapi.php
@@ -186,7 +186,7 @@ class TwitterapiAction extends Action
$twitter_status['favorited'] = false;
}
- # Enclosures
+ // Enclosures
$attachments = $notice->attachments();
if (!empty($attachments)) {
@@ -213,12 +213,50 @@ class TwitterapiAction extends Action
return $twitter_status;
}
+ function twitter_group_array($group)
+ {
+ $twitter_group=array();
+ $twitter_group['id']=$group->id;
+ $twitter_group['url']=$group->permalink();
+ $twitter_group['nickname']=$group->nickname;
+ $twitter_group['fullname']=$group->fullname;
+ $twitter_group['homepage_url']=$group->homepage_url;
+ $twitter_group['original_logo']=$group->original_logo;
+ $twitter_group['homepage_logo']=$group->homepage_logo;
+ $twitter_group['stream_logo']=$group->stream_logo;
+ $twitter_group['mini_logo']=$group->mini_logo;
+ $twitter_group['homepage']=$group->homepage;
+ $twitter_group['description']=$group->description;
+ $twitter_group['location']=$group->location;
+ $twitter_group['created']=$this->date_twitter($group->created);
+ $twitter_group['modified']=$this->date_twitter($group->modified);
+ 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();
$entry = array();
- # We trim() to avoid extraneous whitespace in the output
+ // We trim() to avoid extraneous whitespace in the output
$entry['content'] = common_xml_safe_str(trim($notice->rendered));
$entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
@@ -231,7 +269,26 @@ class TwitterapiAction extends Action
$entry['updated'] = $entry['published'];
$entry['author'] = $profile->getBestName();
- # Enclosure
+ // 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($enclosures)) {
+ $entry['enclosures'] = $enclosures;
+ }
+
+/*
+ // Enclosure
$attachments = $notice->attachments();
if($attachments){
$entry['enclosures']=array();
@@ -245,8 +302,20 @@ class TwitterapiAction extends Action
}
}
}
+*/
+
+ // Tags/Categories
+ $tag = new Notice_tag();
+ $tag->notice_id = $notice->id;
+ if ($tag->find()) {
+ $entry['tags']=array();
+ while ($tag->fetch()) {
+ $entry['tags'][]=$tag->tag;
+ }
+ }
+ $tag->free();
- # RSS Item specific
+ // RSS Item specific
$entry['description'] = $entry['content'];
$entry['pubDate'] = common_date_rfc2822($notice->created);
$entry['guid'] = $entry['link'];
@@ -382,6 +451,15 @@ class TwitterapiAction extends Action
$this->elementEnd('status');
}
+ function show_twitter_xml_group($twitter_group)
+ {
+ $this->elementStart('group');
+ foreach($twitter_group as $element => $value) {
+ $this->element($element, null, $value);
+ }
+ $this->elementEnd('group');
+ }
+
function show_twitter_xml_user($twitter_user, $role='user')
{
$this->elementStart($role);
@@ -419,10 +497,16 @@ class TwitterapiAction extends Action
$this->element('link', null, $entry['link']);
# RSS only supports 1 enclosure per item
- if($entry['enclosures']){
+ if(array_key_exists('enclosures', $entry) and !empty($entry['enclosures'])){
$enclosure = $entry['enclosures'][0];
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
}
+
+ if(array_key_exists('tags', $entry)){
+ foreach($entry['tags'] as $tag){
+ $this->element('category', null,$tag);
+ }
+ }
$this->elementEnd('item');
}
@@ -578,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)
{
@@ -602,6 +745,68 @@ 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');
+ $twitter_group = $this->twitter_group_array($group);
+ $this->show_json_objects($twitter_group);
+ $this->end_document('json');
+ }
+
+ function show_single_xml_group($group)
+ {
+ $this->init_document('xml');
+ $twitter_group = $this->twitter_group_array($group);
+ $this->show_twitter_xml_group($twitter_group);
+ $this->end_document('xml');
+ }
+
// Anyone know what date format this is?
// Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
function date_twitter($dt)
@@ -762,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/util.php b/lib/util.php
index 9e8ec41d2..c8e318efe 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -140,7 +140,7 @@ function common_have_session()
function common_ensure_session()
{
$c = null;
- if (array_key_exists(session_name, $_COOKIE)) {
+ if (array_key_exists(session_name(), $_COOKIE)) {
$c = $_COOKIE[session_name()];
}
if (!common_have_session()) {
@@ -404,7 +404,7 @@ function common_render_text($text)
$r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
$r = common_replace_urls_callback($r, 'common_linkify');
- $r = preg_replace('/(^|\(|\[|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
+ $r = preg_replace('/(^|\(|\[|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
// XXX: machine tags
return $r;
}
@@ -414,9 +414,9 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
$regex = '#'.
'(?:'.
'(?:'.
- '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|xmpp|irc)://'.
+ '(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'.
'|'.
- '(?:mailto|aim|tel):'.
+ '(?:mailto|aim|tel|xmpp):'.
')'.
'[^.\s]+\.[^\s]+'.
'|'.
@@ -595,7 +595,8 @@ function common_tag_link($tag)
function common_canonical_tag($tag)
{
- return strtolower(str_replace(array('-', '_', '.'), '', $tag));
+ $tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8");
+ return str_replace(array('-', '_', '.'), '', $tag);
}
function common_valid_profile_tag($str)
@@ -1409,20 +1410,21 @@ function common_client_ip()
return null;
}
- if ($_SERVER['HTTP_X_FORWARDED_FOR']) {
- if ($_SERVER['HTTP_CLIENT_IP']) {
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
+ if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
$proxy = $_SERVER['HTTP_CLIENT_IP'];
} else {
$proxy = $_SERVER['REMOTE_ADDR'];
}
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
- if ($_SERVER['HTTP_CLIENT_IP']) {
+ $proxy = null;
+ if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
}
- return array($ip, $proxy);
+ return array($proxy, $ip);
}