summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/APCPlugin.php11
-rw-r--r--plugins/Authentication/AuthenticationPlugin.php240
-rw-r--r--plugins/Authentication/User_username.php61
-rw-r--r--plugins/Authorization/AuthorizationPlugin.php108
-rw-r--r--plugins/Autocomplete/AutocompletePlugin.php11
-rw-r--r--plugins/Autocomplete/README (renamed from plugins/Autocomplete/readme.txt)0
-rw-r--r--plugins/BitlyUrl/BitlyUrlPlugin.php14
-rw-r--r--plugins/Blacklist/BlacklistPlugin.php13
-rw-r--r--plugins/CacheLogPlugin.php11
-rw-r--r--plugins/CasAuthentication/CasAuthenticationPlugin.php14
-rw-r--r--plugins/EmailAuthentication/EmailAuthenticationPlugin.php11
-rw-r--r--plugins/Facebook/FacebookPlugin.php15
-rw-r--r--plugins/FirePHP/FirePHPPlugin.php11
-rw-r--r--plugins/FirePHP/README2
-rw-r--r--plugins/GeoURLPlugin.php12
-rw-r--r--plugins/GeonamesPlugin.php12
-rw-r--r--plugins/GoogleAnalyticsPlugin.php12
-rw-r--r--plugins/Gravatar/GravatarPlugin.php12
-rw-r--r--plugins/Imap/ImapPlugin.php96
-rw-r--r--plugins/Imap/README32
-rwxr-xr-xplugins/Imap/imapdaemon.php147
-rw-r--r--plugins/InfiniteScroll/InfiniteScrollPlugin.php11
-rw-r--r--plugins/InfiniteScroll/README (renamed from plugins/InfiniteScroll/readme.txt)0
-rw-r--r--plugins/LdapAuthentication/LdapAuthenticationPlugin.php14
-rw-r--r--plugins/LdapAuthorization/LdapAuthorizationPlugin.php13
-rw-r--r--plugins/LilUrl/LilUrlPlugin.php17
-rw-r--r--plugins/LinkbackPlugin.php14
-rw-r--r--plugins/Mapstraction/MapstractionPlugin.php15
-rw-r--r--plugins/MemcachePlugin.php22
-rw-r--r--plugins/Minify/MinifyPlugin.php13
-rw-r--r--plugins/Mollom/MollomPlugin.php893
-rw-r--r--plugins/Mollom/README22
-rw-r--r--plugins/OpenID/OpenIDPlugin.php17
-rw-r--r--plugins/OpenID/User_openid_trustroot.php2
-rw-r--r--plugins/OpenID/finishopenidlogin.php1
-rw-r--r--plugins/PiwikAnalyticsPlugin.php12
-rw-r--r--plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php21
-rw-r--r--plugins/PtitUrl/PtitUrlPlugin.php13
-rw-r--r--plugins/PubSubHubBub/PubSubHubBubPlugin.php12
-rw-r--r--plugins/RSSCloud/LoggingAggregator.php140
-rw-r--r--plugins/RSSCloud/README54
-rw-r--r--plugins/RSSCloud/RSSCloudNotifier.php240
-rw-r--r--plugins/RSSCloud/RSSCloudPlugin.php295
-rwxr-xr-xplugins/RSSCloud/RSSCloudQueueHandler.php78
-rw-r--r--plugins/RSSCloud/RSSCloudRequestNotify.php347
-rw-r--r--plugins/RSSCloud/RSSCloudSubscription.php79
-rw-r--r--plugins/Realtime/RealtimePlugin.php3
-rw-r--r--plugins/Realtime/json2.js478
-rw-r--r--plugins/Recaptcha/RecaptchaPlugin.php3
-rw-r--r--plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php11
-rw-r--r--plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php13
-rw-r--r--plugins/Sample/SamplePlugin.php11
-rw-r--r--plugins/SimpleUrl/SimpleUrlPlugin.php13
-rw-r--r--plugins/TemplatePlugin.php112
-rw-r--r--plugins/TightUrl/TightUrlPlugin.php12
-rw-r--r--plugins/TwitterBridge/TwitterBridgePlugin.php15
-rw-r--r--plugins/UserFlag/UserFlagPlugin.php51
-rw-r--r--plugins/UserFlag/User_flag_profile.php13
-rw-r--r--plugins/WikiHashtagsPlugin.php15
-rw-r--r--plugins/XCachePlugin.php11
60 files changed, 2976 insertions, 975 deletions
diff --git a/plugins/APCPlugin.php b/plugins/APCPlugin.php
index 18409e29e..666f64b14 100644
--- a/plugins/APCPlugin.php
+++ b/plugins/APCPlugin.php
@@ -104,5 +104,16 @@ class APCPlugin extends Plugin
Event::handle('EndCacheDelete', array($key));
return false;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'APC',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:APC',
+ 'rawdescription' =>
+ _m('Use the <a href="http://pecl.php.net/package/apc">APC</a> variable cache to cache query results.'));
+ return true;
+ }
}
diff --git a/plugins/Authentication/AuthenticationPlugin.php b/plugins/Authentication/AuthenticationPlugin.php
deleted file mode 100644
index 75e8d2b76..000000000
--- a/plugins/Authentication/AuthenticationPlugin.php
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Superclass for plugins that do authentication and/or authorization
- *
- * 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 Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Superclass for plugins that do authentication
- *
- * @category Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-abstract class AuthenticationPlugin extends Plugin
-{
- //is this plugin authoritative for authentication?
- public $authoritative = false;
-
- //should accounts be automatically created after a successful login attempt?
- public $autoregistration = false;
-
- //can the user change their email address
- public $password_changeable=true;
-
- //unique name for this authentication provider
- public $provider_name;
-
- //------------Auth plugin should implement some (or all) of these methods------------\\
- /**
- * Check if a nickname/password combination is valid
- * @param username
- * @param password
- * @return boolean true if the credentials are valid, false if they are invalid.
- */
- function checkPassword($username, $password)
- {
- return false;
- }
-
- /**
- * Automatically register a user when they attempt to login with valid credentials.
- * User::register($data) is a very useful method for this implementation
- * @param username
- * @return mixed instance of User, or false (if user couldn't be created)
- */
- function autoRegister($username)
- {
- $registration_data = array();
- $registration_data['nickname'] = $username ;
- return User::register($registration_data);
- }
-
- /**
- * Change a user's password
- * The old password has been verified to be valid by this plugin before this call is made
- * @param username
- * @param oldpassword
- * @param newpassword
- * @return boolean true if the password was changed, false if password changing failed for some reason
- */
- function changePassword($username,$oldpassword,$newpassword)
- {
- return false;
- }
-
- //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
- function onInitializePlugin(){
- if(!isset($this->provider_name)){
- throw new Exception("must specify a provider_name for this authentication provider");
- }
- }
-
- /**
- * Internal AutoRegister event handler
- * @param nickname
- * @param provider_name
- * @param user - the newly registered user
- */
- function onAutoRegister($nickname, $provider_name, &$user)
- {
- if($provider_name == $this->provider_name && $this->autoregistration){
- $user = $this->autoregister($nickname);
- if($user){
- User_username::register($user,$nickname,$this->provider_name);
- return false;
- }
- }
- }
-
- function onStartCheckPassword($nickname, $password, &$authenticatedUser){
- //map the nickname to a username
- $user_username = new User_username();
- $user_username->username=$nickname;
- $user_username->provider_name=$this->provider_name;
- if($user_username->find() && $user_username->fetch()){
- $username = $user_username->username;
- $authenticated = $this->checkPassword($username, $password);
- if($authenticated){
- $authenticatedUser = User::staticGet('id', $user_username->user_id);
- return false;
- }
- }else{
- $user = User::staticGet('nickname', $nickname);
- if($user){
- //make sure a different provider isn't handling this nickname
- $user_username = new User_username();
- $user_username->username=$nickname;
- if(!$user_username->find()){
- //no other provider claims this username, so it's safe for us to handle it
- $authenticated = $this->checkPassword($nickname, $password);
- if($authenticated){
- $authenticatedUser = User::staticGet('nickname', $nickname);
- User_username::register($authenticatedUser,$nickname,$this->provider_name);
- return false;
- }
- }
- }else{
- $authenticated = $this->checkPassword($nickname, $password);
- if($authenticated){
- if(Event::handle('AutoRegister', array($nickname, $this->provider_name, &$authenticatedUser))){
- if($authenticatedUser){
- return false;
- }
- }
- }
- }
- }
- if($this->authoritative){
- return false;
- }else{
- //we're not authoritative, so let other handlers try
- return;
- }
- }
-
- function onStartChangePassword($user,$oldpassword,$newpassword)
- {
- if($this->password_changeable){
- $user_username = new User_username();
- $user_username->user_id=$user->id;
- $user_username->provider_name=$this->provider_name;
- if($user_username->find() && $user_username->fetch()){
- $authenticated = $this->checkPassword($user_username->username, $oldpassword);
- if($authenticated){
- $result = $this->changePassword($user_username->username,$oldpassword,$newpassword);
- if($result){
- //stop handling of other handlers, because what was requested was done
- return false;
- }else{
- throw new Exception(_('Password changing failed'));
- }
- }else{
- if($this->authoritative){
- //since we're authoritative, no other plugin could do this
- throw new Exception(_('Password changing failed'));
- }else{
- //let another handler try
- return null;
- }
- }
- }
- }else{
- if($this->authoritative){
- //since we're authoritative, no other plugin could do this
- throw new Exception(_('Password changing is not allowed'));
- }
- }
- }
-
- function onStartAccountSettingsPasswordMenuItem($widget)
- {
- if($this->authoritative && !$this->password_changeable){
- //since we're authoritative, no other plugin could change passwords, so do not render the menu item
- return false;
- }
- }
-
- function onAutoload($cls)
- {
- switch ($cls)
- {
- case 'User_username':
- require_once(INSTALLDIR.'/plugins/Authentication/User_username.php');
- return false;
- default:
- return true;
- }
- }
-
- function onCheckSchema() {
- $schema = Schema::get();
- $schema->ensureTable('user_username',
- array(new ColumnDef('provider_name', 'varchar',
- '255', false, 'PRI'),
- new ColumnDef('username', 'varchar',
- '255', false, 'PRI'),
- new ColumnDef('user_id', 'integer',
- null, false),
- new ColumnDef('created', 'datetime',
- null, false),
- new ColumnDef('modified', 'timestamp')));
- return true;
- }
-
- function onUserDeleteRelated($user, &$tables)
- {
- $tables[] = 'User_username';
- return true;
- }
-}
-
diff --git a/plugins/Authentication/User_username.php b/plugins/Authentication/User_username.php
deleted file mode 100644
index 853fd5cb8..000000000
--- a/plugins/Authentication/User_username.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * Table Definition for user_username
- */
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
-
-class User_username extends Memcached_DataObject
-{
- ###START_AUTOCODE
- /* the code below is auto generated do not remove the above tag */
-
- public $__table = 'user_username'; // table name
- public $user_id; // int(4) not_null
- public $provider_name; // varchar(255) primary_key not_null
- public $username; // varchar(255) primary_key not_null
- public $created; // datetime() not_null
- public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
-
- /* Static get */
- function staticGet($k,$v=null)
- { return Memcached_DataObject::staticGet('User_username',$k,$v); }
-
- /* the code above is auto generated do not remove the tag below */
- ###END_AUTOCODE
-
- /**
- * Register a user with a username on a given provider
- * @param User User object
- * @param string username on the given provider
- * @param provider_name string name of the provider
- * @return mixed User_username instance if the registration succeeded, false if it did not
- */
- static function register($user, $username, $provider_name)
- {
- $user_username = new User_username();
- $user_username->user_id = $user->id;
- $user_username->provider_name = $provider_name;
- $user_username->username = $username;
- $user_username->created = DB_DataObject_Cast::dateTime();
- if($user_username->insert()){
- return $user_username;
- }else{
- return false;
- }
- }
-
- function table() {
- return array(
- 'user_id' => DB_DATAOBJECT_INT,
- 'username' => DB_DATAOBJECT_STR,
- 'provider_name' => DB_DATAOBJECT_STR ,
- 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME
- );
- }
-
- // now define the keys.
- function keys() {
- return array('provider_name', 'username');
- }
-
-}
diff --git a/plugins/Authorization/AuthorizationPlugin.php b/plugins/Authorization/AuthorizationPlugin.php
deleted file mode 100644
index e4e046d08..000000000
--- a/plugins/Authorization/AuthorizationPlugin.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Superclass for plugins that do authorization
- *
- * 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 Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
-
-/**
- * Superclass for plugins that do authorization
- *
- * @category Plugin
- * @package StatusNet
- * @author Craig Andrews <candrews@integralblue.com>
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link http://status.net/
- */
-
-abstract class AuthorizationPlugin extends Plugin
-{
- //is this plugin authoritative for authorization?
- public $authoritative = false;
-
- //------------Auth plugin should implement some (or all) of these methods------------\\
-
- /**
- * Is a user allowed to log in?
- * @param user
- * @return boolean true if the user is allowed to login, false if explicitly not allowed to login, null if we don't explicitly allow or deny login
- */
- function loginAllowed($user) {
- return null;
- }
-
- /**
- * Does a profile grant the user a named role?
- * @param profile
- * @return boolean true if the profile has the role, false if not
- */
- function hasRole($profile, $name) {
- return false;
- }
-
- //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
- function onInitializePlugin(){
-
- }
-
- function onStartSetUser(&$user) {
- $loginAllowed = $this->loginAllowed($user);
- if($loginAllowed === true){
- return;
- }else if($loginAllowed === false){
- $user = null;
- return false;
- }else{
- if($this->authoritative) {
- $user = null;
- return false;
- }else{
- return;
- }
- }
- }
-
- function onStartSetApiUser(&$user) {
- return $this->onStartSetUser(&$user);
- }
-
- function onStartHasRole($profile, $name, &$has_role) {
- if($this->hasRole($profile, $name)){
- $has_role = true;
- return false;
- }else{
- if($this->authoritative) {
- $has_role = false;
- return false;
- }else{
- return;
- }
- }
- }
-}
-
diff --git a/plugins/Autocomplete/AutocompletePlugin.php b/plugins/Autocomplete/AutocompletePlugin.php
index baaec73c1..d586631a4 100644
--- a/plugins/Autocomplete/AutocompletePlugin.php
+++ b/plugins/Autocomplete/AutocompletePlugin.php
@@ -61,5 +61,16 @@ class AutocompletePlugin extends Plugin
}
}
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Autocomplete',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:Autocomplete',
+ 'rawdescription' =>
+ _m('The autocomplete plugin allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user\'s friend\' screen names.'));
+ return true;
+ }
+
}
?>
diff --git a/plugins/Autocomplete/readme.txt b/plugins/Autocomplete/README
index 1db4c6565..1db4c6565 100644
--- a/plugins/Autocomplete/readme.txt
+++ b/plugins/Autocomplete/README
diff --git a/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php
index 65d0f70e6..f7f28b4d6 100644
--- a/plugins/BitlyUrl/BitlyUrlPlugin.php
+++ b/plugins/BitlyUrl/BitlyUrlPlugin.php
@@ -49,6 +49,18 @@ class BitlyUrlPlugin extends UrlShortenerPlugin
if(!$response) return;
return current(json_decode($response)->results)->hashUrl;
}
-}
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => sprintf('BitlyUrl (%s)', $this->shortenerName),
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:BitlyUrl',
+ 'rawdescription' =>
+ sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
+ $this->shortenerName));
+
+ return true;
+ }
+}
diff --git a/plugins/Blacklist/BlacklistPlugin.php b/plugins/Blacklist/BlacklistPlugin.php
index 655b0926b..84a2cb616 100644
--- a/plugins/Blacklist/BlacklistPlugin.php
+++ b/plugins/Blacklist/BlacklistPlugin.php
@@ -43,6 +43,8 @@ if (!defined('STATUSNET')) {
class BlacklistPlugin extends Plugin
{
+ const VERSION = STATUSNET_VERSION;
+
public $nicknames = array();
public $urls = array();
@@ -200,4 +202,15 @@ class BlacklistPlugin extends Plugin
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Blacklist',
+ 'version' => self::VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Blacklist',
+ 'description' =>
+ _m('Keep a blacklist of forbidden nickname and URL patterns.'));
+ return true;
+ }
}
diff --git a/plugins/CacheLogPlugin.php b/plugins/CacheLogPlugin.php
index f1e5dd83a..4c47de80e 100644
--- a/plugins/CacheLogPlugin.php
+++ b/plugins/CacheLogPlugin.php
@@ -106,5 +106,16 @@ class CacheLogPlugin extends Plugin
$this->log(LOG_INFO, "Done deleting cache value for key '$key'");
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'CacheLog',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:CacheLog',
+ 'description' =>
+ _m('Log reads and writes to the cache'));
+ return true;
+ }
}
diff --git a/plugins/CasAuthentication/CasAuthenticationPlugin.php b/plugins/CasAuthentication/CasAuthenticationPlugin.php
index 8f29c7d2a..483b060ab 100644
--- a/plugins/CasAuthentication/CasAuthenticationPlugin.php
+++ b/plugins/CasAuthentication/CasAuthenticationPlugin.php
@@ -34,7 +34,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
// We bundle the phpCAS library...
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/CAS');
-require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php';
class CasAuthenticationPlugin extends AuthenticationPlugin
{
public $server;
@@ -58,8 +57,6 @@ class CasAuthenticationPlugin extends AuthenticationPlugin
case 'CasloginAction':
require_once(INSTALLDIR.'/plugins/CasAuthentication/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
return false;
- default:
- return parent::onAutoload($cls);
}
}
@@ -141,4 +138,15 @@ class CasAuthenticationPlugin extends AuthenticationPlugin
$casSettings['port']=$this->port;
$casSettings['path']=$this->path;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'CAS Authentication',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:CasAuthentication',
+ 'rawdescription' =>
+ _m('The CAS Authentication plugin allows for StatusNet to handle authentication through CAS (Central Authentication Service).'));
+ return true;
+ }
}
diff --git a/plugins/EmailAuthentication/EmailAuthenticationPlugin.php b/plugins/EmailAuthentication/EmailAuthenticationPlugin.php
index 25e537735..406c00073 100644
--- a/plugins/EmailAuthentication/EmailAuthenticationPlugin.php
+++ b/plugins/EmailAuthentication/EmailAuthenticationPlugin.php
@@ -50,5 +50,16 @@ class EmailAuthenticationPlugin extends Plugin
}
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Email Authentication',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:EmailAuthentication',
+ 'rawdescription' =>
+ _m('The Email Authentication plugin allows users to login using their email address.'));
+ return true;
+ }
}
diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php
index 39b2ef287..de91bf24a 100644
--- a/plugins/Facebook/FacebookPlugin.php
+++ b/plugins/Facebook/FacebookPlugin.php
@@ -32,6 +32,7 @@ if (!defined('STATUSNET')) {
}
define("FACEBOOK_CONNECT_SERVICE", 3);
+define('FACEBOOKPLUGIN_VERSION', '0.9');
require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
@@ -554,4 +555,18 @@ class FacebookPlugin extends Plugin
return true;
}
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Facebook',
+ 'version' => FACEBOOKPLUGIN_VERSION,
+ 'author' => 'Zach Copley',
+ 'homepage' => 'http://status.net/wiki/Plugin:Facebook',
+ 'rawdescription' =>
+ _m('The Facebook plugin allows you to integrate ' .
+ 'your StatusNet instance with ' .
+ '<a href="http://facebook.com/">Facebook</a> ' .
+ 'and Facebook Connect.'));
+ return true;
+ }
+
}
diff --git a/plugins/FirePHP/FirePHPPlugin.php b/plugins/FirePHP/FirePHPPlugin.php
index 37b397796..452f79024 100644
--- a/plugins/FirePHP/FirePHPPlugin.php
+++ b/plugins/FirePHP/FirePHPPlugin.php
@@ -55,5 +55,16 @@ class FirePHPPlugin extends Plugin
$priority = $firephp_priorities[$priority];
$this->firephp->fb($msg, $priority);
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'FirePHP',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:FirePHP',
+ 'rawdescription' =>
+ _m('The FirePHP plugin writes StatusNet\'s log output to FirePHP.'));
+ return true;
+ }
}
diff --git a/plugins/FirePHP/README b/plugins/FirePHP/README
index ee22794d5..22ed1e9be 100644
--- a/plugins/FirePHP/README
+++ b/plugins/FirePHP/README
@@ -1,4 +1,4 @@
-The FirePHP writes StatusNet's log output to FirePHP.
+The FirePHP plugin writes StatusNet's log output to FirePHP.
Using FirePHP on production sites can expose sensitive information.
You must protect the security of your application by disabling FirePHP
diff --git a/plugins/GeoURLPlugin.php b/plugins/GeoURLPlugin.php
index 30ff2c278..01178f39c 100644
--- a/plugins/GeoURLPlugin.php
+++ b/plugins/GeoURLPlugin.php
@@ -116,4 +116,16 @@ class GeoURLPlugin extends Plugin
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'GeoURL',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:GeoURL',
+ 'rawdescription' =>
+ _m('Ping <a href="http://geourl.org/">GeoURL</a> when '.
+ 'new geolocation-enhanced notices are posted.'));
+ return true;
+ }
}
diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php
index 805166eaa..52cc9c97f 100644
--- a/plugins/GeonamesPlugin.php
+++ b/plugins/GeonamesPlugin.php
@@ -426,4 +426,16 @@ class GeonamesPlugin extends Plugin
return $document->geoname;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Geonames',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Geonames',
+ 'rawdescription' =>
+ _m('Uses <a href="http://geonames.org/">Geonames</a> service to get human-readable '.
+ 'names for locations based on user-provided lat/long pairs.'));
+ return true;
+ }
}
diff --git a/plugins/GoogleAnalyticsPlugin.php b/plugins/GoogleAnalyticsPlugin.php
index 6891ee6a7..c646bf113 100644
--- a/plugins/GoogleAnalyticsPlugin.php
+++ b/plugins/GoogleAnalyticsPlugin.php
@@ -70,4 +70,16 @@ class GoogleAnalyticsPlugin extends Plugin
$action->inlineScript($js1);
$action->inlineScript($js2);
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'GoogleAnalytics',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:GoogleAnalytics',
+ 'rawdescription' =>
+ _m('Use <a href="http://www.google.com/analytics/">Google Analytics</a>'.
+ ' to track Web access.'));
+ return true;
+ }
}
diff --git a/plugins/Gravatar/GravatarPlugin.php b/plugins/Gravatar/GravatarPlugin.php
index 3c61a682e..580852072 100644
--- a/plugins/Gravatar/GravatarPlugin.php
+++ b/plugins/Gravatar/GravatarPlugin.php
@@ -185,4 +185,16 @@ class GravatarPlugin extends Plugin
"&size=".$size;
return $url;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Gravatar',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Eric Helgeson',
+ 'homepage' => 'http://status.net/wiki/Plugin:Gravatar',
+ 'rawdescription' =>
+ _m('The Gravatar plugin allows users to use their <a href="http://www.gravatar.com/">Gravatar</a> with StatusNet.'));
+
+ return true;
+ }
}
diff --git a/plugins/Imap/ImapPlugin.php b/plugins/Imap/ImapPlugin.php
new file mode 100644
index 000000000..d9768b680
--- /dev/null
+++ b/plugins/Imap/ImapPlugin.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to add a StatusNet Facebook application
+ *
+ * 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 Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * IMAP plugin to allow StatusNet to grab incoming emails and handle them as new user posts
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class ImapPlugin extends Plugin
+{
+ public $mailbox;
+ public $user;
+ public $password;
+ public $poll_frequency = 60;
+ public static $instances = array();
+ public static $daemon_added = array();
+
+ function initialize(){
+ if(!isset($this->mailbox)){
+ throw new Exception("must specify a mailbox");
+ }
+ if(!isset($this->user)){
+ throw new Exception("must specify a user");
+ }
+ if(!isset($this->password)){
+ throw new Exception("must specify a password");
+ }
+ if(!isset($this->poll_frequency)){
+ throw new Exception("must specify a poll_frequency");
+ }
+
+ self::$instances[] = $this;
+ return true;
+ }
+
+ function cleanup(){
+ $index = array_search($this, self::$instances);
+ unset(self::$instances[$index]);
+ return true;
+ }
+
+ function onGetValidDaemons($daemons)
+ {
+ if(! self::$daemon_added){
+ array_push($daemons, INSTALLDIR .
+ '/plugins/Imap/imapdaemon.php');
+ self::$daemon_added = true;
+ }
+ return true;
+ }
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'IMAP',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:IMAP',
+ 'rawdescription' =>
+ _m('The IMAP plugin allows for StatusNet to check a POP or IMAP mailbox for incoming mail containing user posts.'));
+ return true;
+ }
+}
diff --git a/plugins/Imap/README b/plugins/Imap/README
new file mode 100644
index 000000000..640a411a8
--- /dev/null
+++ b/plugins/Imap/README
@@ -0,0 +1,32 @@
+The IMAP plugin allows for StatusNet to check a POP or IMAP mailbox for
+incoming mail containing user posts.
+
+Installation
+============
+addPlugin('imap', array(
+ 'mailbox' => '...',
+ 'user' => '...',
+ 'password' => '...'
+));
+to the bottom of your config.php
+
+Also, make sure:
+$config['mail']['domain'] = 'yourdomain.example.net';
+is set in your config.php
+
+Create a catch-all account for your domain, and use this account with this
+plugin. Whenever a user sends a message to their personal notice posting
+address, the message should end up in this mailbox, and then the plugin daemon
+will pick it up and post the notice on the user's behalf.
+
+The daemon included with this plugin must be running. It will be started by
+the plugin along with their other daemons when you run scripts/startdaemons.sh.
+See the StatusNet README for more about queuing and daemons.
+
+Settings
+========
+mailbox*: the mailbox specifier.
+ See http://www.php.net/manual/en/function.imap-open.php for details
+user*: username to use when authenticating to the mailbox
+password*: password to use when authenticating to the mailbox
+poll_frequency: how often (in seconds) to check for new messages
diff --git a/plugins/Imap/imapdaemon.php b/plugins/Imap/imapdaemon.php
new file mode 100755
index 000000000..7e60e1376
--- /dev/null
+++ b/plugins/Imap/imapdaemon.php
@@ -0,0 +1,147 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+$shortoptions = 'fi::';
+$longoptions = array('id::', 'foreground');
+
+$helptext = <<<END_OF_IMAP_HELP
+Daemon script for receiving new notices from users via a mail box (IMAP, POP3, etc)
+
+ -i --id Identity (default none)
+ -f --foreground Stay in the foreground (default background)
+
+END_OF_IMAP_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/common.php';
+require_once INSTALLDIR . '/lib/daemon.php';
+require_once INSTALLDIR.'/lib/mailhandler.php';
+
+class IMAPDaemon extends Daemon
+{
+ function __construct($resource=null, $daemonize=true, $attrs)
+ {
+ parent::__construct($daemonize);
+
+ foreach ($attrs as $attr=>$value)
+ {
+ $this->$attr = $value;
+ }
+
+ $this->log(LOG_INFO, "INITIALIZE IMAPDaemon {" . $this->name() . "}");
+ }
+
+ function name()
+ {
+ return strtolower('imapdaemon.'.$this->user.'.'.crc32($this->mailbox));
+ }
+
+ function run()
+ {
+ $this->connect();
+ while(true)
+ {
+ if(imap_ping($this->conn) || $this->connect())
+ {
+ $this->check_mailbox();
+ }
+ sleep($this->poll_frequency);
+ }
+ }
+
+ function check_mailbox()
+ {
+ $count = imap_num_msg($this->conn);
+ $this->log(LOG_INFO, "Found $count messages");
+ if($count > 0){
+ $handler = new IMAPMailHandler();
+ for($i=1; $i <= $count; $i++)
+ {
+ $rawmessage = imap_fetchheader($this->conn, $count, FT_PREFETCHTEXT) . imap_body($this->conn, $i);
+ $handler->handle_message($rawmessage);
+ imap_delete($this->conn, $i);
+ }
+ imap_expunge($this->conn);
+ $this->log(LOG_INFO, "Finished processing messages");
+ }
+ }
+
+ function log($level, $msg)
+ {
+ $text = $this->name() . ': '.$msg;
+ common_log($level, $text);
+ if (!$this->daemonize)
+ {
+ $line = common_log_line($level, $text);
+ echo $line;
+ echo "\n";
+ }
+ }
+
+ function connect()
+ {
+ $this->conn = imap_open($this->mailbox, $this->user, $this->password);
+ if($this->conn){
+ $this->log(LOG_INFO, "Connected");
+ return true;
+ }else{
+ $this->log(LOG_INFO, "Failed to connect: " . imap_last_error());
+ return false;
+ }
+ }
+}
+
+class IMAPMailHandler extends MailHandler
+{
+ function error($from, $msg)
+ {
+ $this->log(LOG_INFO, "Error: $from $msg");
+ $headers['To'] = $from;
+ $headers['Subject'] = _m('Error');
+
+ return mail_send(array($from), $headers, $msg);
+ }
+}
+
+if (have_option('i', 'id')) {
+ $id = get_option_value('i', 'id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+$foreground = have_option('f', 'foreground');
+
+foreach(ImapPlugin::$instances as $pluginInstance){
+
+ $daemon = new IMAPDaemon($id, !$foreground, array(
+ 'mailbox' => $pluginInstance->mailbox,
+ 'user' => $pluginInstance->user,
+ 'password' => $pluginInstance->password,
+ 'poll_frequency' => $pluginInstance->poll_frequency
+ ));
+
+ $daemon->runOnce();
+
+}
diff --git a/plugins/InfiniteScroll/InfiniteScrollPlugin.php b/plugins/InfiniteScroll/InfiniteScrollPlugin.php
index 5928c007f..a4d1a5d05 100644
--- a/plugins/InfiniteScroll/InfiniteScrollPlugin.php
+++ b/plugins/InfiniteScroll/InfiniteScrollPlugin.php
@@ -43,4 +43,15 @@ class InfiniteScrollPlugin extends Plugin
$action->script('plugins/InfiniteScroll/jquery.infinitescroll.js');
$action->script('plugins/InfiniteScroll/infinitescroll.js');
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'InfiniteScroll',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:InfiniteScroll',
+ 'rawdescription' =>
+ _m('Infinite Scroll adds the following functionality to your StatusNet installation: When a user scrolls towards the bottom of the page, the next page of notices is automatically retrieved and appended. This means they never need to click "Next Page", which dramatically increases stickiness.'));
+ return true;
+ }
}
diff --git a/plugins/InfiniteScroll/readme.txt b/plugins/InfiniteScroll/README
index 2428cc69a..2428cc69a 100644
--- a/plugins/InfiniteScroll/readme.txt
+++ b/plugins/InfiniteScroll/README
diff --git a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php
index 39967fe42..eb3a05117 100644
--- a/plugins/LdapAuthentication/LdapAuthenticationPlugin.php
+++ b/plugins/LdapAuthentication/LdapAuthenticationPlugin.php
@@ -31,7 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php';
require_once 'Net/LDAP2.php';
class LdapAuthenticationPlugin extends AuthenticationPlugin
@@ -75,8 +74,6 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
case 'MemcacheSchemaCache':
require_once(INSTALLDIR.'/plugins/LdapAuthentication/MemcacheSchemaCache.php');
return false;
- default:
- return parent::onAutoload($cls);
}
}
@@ -349,4 +346,15 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
return $str;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'LDAP Authentication',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:LdapAuthentication',
+ 'rawdescription' =>
+ _m('The LDAP Authentication plugin allows for StatusNet to handle authentication through LDAP.'));
+ return true;
+ }
}
diff --git a/plugins/LdapAuthorization/LdapAuthorizationPlugin.php b/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
index 5e759c379..7f48ce5e1 100644
--- a/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
+++ b/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
@@ -31,7 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/plugins/Authorization/AuthorizationPlugin.php';
require_once 'Net/LDAP2.php';
class LdapAuthorizationPlugin extends AuthorizationPlugin
@@ -53,7 +52,6 @@ class LdapAuthorizationPlugin extends AuthorizationPlugin
public $attributes = array();
function onInitializePlugin(){
- parent::onInitializePlugin();
if(!isset($this->host)){
throw new Exception("must specify a host");
}
@@ -208,4 +206,15 @@ class LdapAuthorizationPlugin extends AuthorizationPlugin
return false;
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'LDAP Authorization',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:LdapAuthorization',
+ 'rawdescription' =>
+ _m('The LDAP Authorization plugin allows for StatusNet to handle authorization through LDAP.'));
+ return true;
+ }
}
diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php
index 4a6f1cdc7..c3e37c0c0 100644
--- a/plugins/LilUrl/LilUrlPlugin.php
+++ b/plugins/LilUrl/LilUrlPlugin.php
@@ -46,9 +46,9 @@ class LilUrlPlugin extends UrlShortenerPlugin
protected function shorten($url) {
$data = array('longurl' => $url);
-
+
$responseBody = $this->http_post($this->serviceUrl,$data);
-
+
if (!$responseBody) return;
$y = @simplexml_load_string($responseBody);
if (!isset($y->body)) return;
@@ -57,5 +57,18 @@ class LilUrlPlugin extends UrlShortenerPlugin
return strval($x['href']);
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => sprintf('LilUrl (%s)', $this->shortenerName),
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:LilUrl',
+ 'rawdescription' =>
+ sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
+ $this->shortenerName));
+
+ return true;
+ }
}
diff --git a/plugins/LinkbackPlugin.php b/plugins/LinkbackPlugin.php
index f220fff8f..15e57ab0e 100644
--- a/plugins/LinkbackPlugin.php
+++ b/plugins/LinkbackPlugin.php
@@ -231,4 +231,18 @@ class LinkbackPlugin extends Plugin
return 'LinkbackPlugin/'.LINKBACKPLUGIN_VERSION .
' StatusNet/' . STATUSNET_VERSION;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Linkback',
+ 'version' => LINKBACKPLUGIN_VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Linkback',
+ 'rawdescription' =>
+ _m('Notify blog authors when their posts have been linked in '.
+ 'microblog notices using '.
+ '<a href="http://www.hixie.ch/specs/pingback/pingback">Pingback</a> '.
+ 'or <a href="http://www.movabletype.org/docs/mttrackback.html">Trackback</a> protocols.'));
+ return true;
+ }
}
diff --git a/plugins/Mapstraction/MapstractionPlugin.php b/plugins/Mapstraction/MapstractionPlugin.php
index 93679e56c..868933fd4 100644
--- a/plugins/Mapstraction/MapstractionPlugin.php
+++ b/plugins/Mapstraction/MapstractionPlugin.php
@@ -47,6 +47,8 @@ if (!defined('STATUSNET')) {
class MapstractionPlugin extends Plugin
{
+ const VERSION = STATUSNET_VERSION;
+
/** provider name, one of:
'cloudmade', 'google', 'microsoft', 'openlayers', 'yahoo' */
public $provider = 'openlayers';
@@ -192,4 +194,17 @@ class MapstractionPlugin extends Plugin
$action->elementEnd('div');
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Mapstraction',
+ 'version' => self::VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Mapstraction',
+ 'rawdescription' =>
+ _m('Show maps of users\' and friends\' notices '.
+ 'with <a href="http://www.mapstraction.com/">Mapstraction</a> '.
+ 'JavaScript library.'));
+ return true;
+ }
}
diff --git a/plugins/MemcachePlugin.php b/plugins/MemcachePlugin.php
index 998766313..5f93e9a83 100644
--- a/plugins/MemcachePlugin.php
+++ b/plugins/MemcachePlugin.php
@@ -57,6 +57,8 @@ class MemcachePlugin extends Plugin
public $compressThreshold = 20480;
public $compressMinSaving = 0.2;
+ public $persistent = null;
+
/**
* Initialize the plugin
*
@@ -67,6 +69,9 @@ class MemcachePlugin extends Plugin
function onInitializePlugin()
{
+ if (is_null($this->persistent)) {
+ $this->persistent = (php_sapi_name() == 'cli') ? false : true;
+ }
$this->_ensureConn();
return true;
}
@@ -149,15 +154,15 @@ class MemcachePlugin extends Plugin
$port = 11211;
}
- $this->_conn->addServer($host, $port);
+ $this->_conn->addServer($host, $port, $this->persistent);
}
} else {
- $this->_conn->addServer($this->servers);
+ $this->_conn->addServer($this->servers, $this->persistent);
list($host, $port) = explode(';', $this->servers);
if (empty($port)) {
$port = 11211;
}
- $this->_conn->addServer($host, $port);
+ $this->_conn->addServer($host, $port, $this->persistent);
}
// Compress items stored in the cache if they're over threshold in size
@@ -171,5 +176,16 @@ class MemcachePlugin extends Plugin
$this->compressMinSaving);
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Memcache',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou, Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:Memcache',
+ 'rawdescription' =>
+ _m('Use <a href="http://memcached.org/">Memcached</a> to cache query results.'));
+ return true;
+ }
}
diff --git a/plugins/Minify/MinifyPlugin.php b/plugins/Minify/MinifyPlugin.php
index 71fade19a..b49b6a4ba 100644
--- a/plugins/Minify/MinifyPlugin.php
+++ b/plugins/Minify/MinifyPlugin.php
@@ -84,7 +84,7 @@ class MinifyPlugin extends Plugin
function onStartScriptElement($action,&$src,&$type) {
$url = parse_url($src);
- if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
+ if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
{
$src = $this->minifyUrl($src);
}
@@ -164,5 +164,16 @@ class MinifyPlugin extends Plugin
require_once('Minify/CSS.php');
return Minify_CSS::minify($code,$options);
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Minify',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:Minify',
+ 'rawdescription' =>
+ _m('The Minify plugin minifies your CSS and Javascript, removing whitespace and comments.'));
+ return true;
+ }
}
diff --git a/plugins/Mollom/MollomPlugin.php b/plugins/Mollom/MollomPlugin.php
new file mode 100644
index 000000000..4c82c481a
--- /dev/null
+++ b/plugins/Mollom/MollomPlugin.php
@@ -0,0 +1,893 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to check submitted notices with Mollom
+ *
+ * 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/>.
+ *
+ * Mollom is a bayesian spam checker, wrapped into a webservice
+ * This plugin is based on the Drupal Mollom module
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Brenda Wallace <brenda@cpan.org>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ *
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+define('MOLLOMPLUGIN_VERSION', '0.1');
+define('MOLLOM_API_VERSION', '1.0');
+
+define('MOLLOM_ANALYSIS_UNKNOWN' , 0);
+define('MOLLOM_ANALYSIS_HAM' , 1);
+define('MOLLOM_ANALYSIS_SPAM' , 2);
+define('MOLLOM_ANALYSIS_UNSURE' , 3);
+
+define('MOLLOM_MODE_DISABLED', 0);
+define('MOLLOM_MODE_CAPTCHA' , 1);
+define('MOLLOM_MODE_ANALYSIS', 2);
+
+define('MOLLOM_FALLBACK_BLOCK' , 0);
+define('MOLLOM_FALLBACK_ACCEPT', 1);
+
+define('MOLLOM_ERROR' , 1000);
+define('MOLLOM_REFRESH' , 1100);
+define('MOLLOM_REDIRECT', 1200);
+
+/**
+ * Plugin to check submitted notices with Mollom
+ *
+ * Mollom is a bayesian spam filter provided by webservice.
+ *
+ * @category Plugin
+ * @package Laconica
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ *
+ * @see Event
+ */
+
+
+
+class MollomPlugin extends Plugin
+{
+ public $public_key;
+ public $private_key;
+ public $servers;
+
+ function onStartNoticeSave($notice)
+ {
+ if ( $this->public_key ) {
+ //Check spam
+ $data = array(
+ 'post_body' => $notice->content,
+ 'author_name' => $profile->nickname,
+ 'author_url' => $profile->homepage,
+ 'author_id' => $profile->id,
+ 'author_ip' => $this->getClientIp(),
+ );
+ $response = $this->mollom('mollom.checkContent', $data);
+ if ($response['spam'] == MOLLOM_ANALYSIS_SPAM) {
+ throw new ClientException(_("Spam Detected"), 400);
+ }
+ if ($response['spam'] == MOLLOM_ANALYSIS_UNSURE) {
+ //if unsure, let through
+ }
+ if($response['spam'] == MOLLOM_ANALYSIS_HAM) {
+ // all good! :-)
+ }
+ }
+
+ return true;
+ }
+
+ function getClientIP() {
+ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ // Note: order matters here; use proxy-forwarded stuff first
+ foreach (array('HTTP_X_FORWARDED_FOR', 'CLIENT-IP', 'REMOTE_ADDR') as $k) {
+ if (isset($_SERVER[$k])) {
+ return $_SERVER[$k];
+ }
+ }
+ }
+ return '127.0.0.1';
+ }
+ /**
+ * Call a remote procedure at the Mollom server. This function will
+ * automatically add the information required to authenticate against
+ * Mollom.
+ */
+ function mollom($method, $data = array()) {
+ if (!extension_loaded('xmlrpc')) {
+ if (!dl('xmlrpc.so')) {
+ common_log(LOG_ERR, "Can't pingback; xmlrpc extension not available.");
+ }
+ }
+
+ // Construct the server URL:
+ $public_key = $this->public_key;
+ // Retrieve the list of Mollom servers from the database:
+ $servers = $this->servers;
+
+ if ($servers == NULL) {
+ // Retrieve a list of valid Mollom servers from mollom.com:
+ $servers = $this->xmlrpc('http://xmlrpc.mollom.com/'. MOLLOM_API_VERSION, 'mollom.getServerList', $this->authentication());
+
+ // Store the list of servers in the database:
+ // TODO! variable_set('mollom_servers', $servers);
+ }
+
+ if (is_array($servers)) {
+ // Send the request to the first server, if that fails, try the other servers in the list:
+ foreach ($servers as $server) {
+ $auth = $this->authentication();
+ $data = array_merge($data, $auth);
+ $result = $this->xmlrpc($server .'/'. MOLLOM_API_VERSION, $method, $data);
+
+ // Debug output:
+ if (isset($data['session_id'])) {
+ common_debug("called $method at server $server with session ID '". $data['session_id'] ."'");
+ }
+ else {
+ common_debug("called $method at server $server with no session ID");
+ }
+
+ if ($errno = $this->xmlrpc_errno()) {
+ common_log(LOG_ERR, sprintf('Error @errno: %s - %s - %s - <pre>%s</pre>', $this->xmlrpc_errno(), $server, $this->xmlrpc_error_msg(), $method, print_r($data, TRUE)));
+
+ if ($errno == MOLLOM_REFRESH) {
+ // Retrieve a list of valid Mollom servers from mollom.com:
+ $servers = $this->xmlrpc('http://xmlrpc.mollom.com/'. MOLLOM_API_VERSION, 'mollom.getServerList', $this->authentication());
+
+ // Store the updated list of servers in the database:
+ //tODO variable_set('mollom_servers', $servers);
+ }
+ else if ($errno == MOLLOM_ERROR) {
+ return $result;
+ }
+ else if ($errno == MOLLOM_REDIRECT) {
+ // Do nothing, we select the next client automatically.
+ }
+
+ // Reset the XMLRPC error:
+ $this->xmlrpc_error(0); // FIXME: this is crazy.
+ }
+ else {
+ common_debug("Result = " . print_r($result, TRUE));
+ return $result;
+ }
+ }
+ }
+
+ // If none of the servers worked, activate the fallback mechanism:
+ common_debug("none of the servers worked");
+ // _mollom_fallback();
+
+ // If everything failed, we reset the server list to force Mollom to request a new list:
+ //TODO variable_set('mollom_servers', array());
+ }
+
+ /**
+ * This function generate an array with all the information required to
+ * authenticate against Mollom. To prevent that requests are forged and
+ * that you are impersonated, each request is signed with a hash computed
+ * based on a private key and a timestamp.
+ *
+ * Both the client and the server share the secret key that is used to
+ * create the authentication hash based on a timestamp. They both hash
+ * the timestamp with the secret key, and if the hashes match, the
+ * authenticity of the message has been validated.
+ *
+ * To avoid that someone can intercept a (hash, timestamp)-pair and
+ * use that to impersonate a client, Mollom will reject the request
+ * when the timestamp is more than 15 minutes off.
+ *
+ * Make sure your server's time is synchronized with the world clocks,
+ * and that you don't share your private key with anyone else.
+ */
+ private function authentication() {
+
+ $public_key = $this->public_key;
+ $private_key = $this->private_key;
+
+ // Generate a timestamp according to the dateTime format (http://www.w3.org/TR/xmlschema-2/#dateTime):
+ $time = gmdate("Y-m-d\TH:i:s.\\0\\0\\0O", time());
+
+ // Calculate a HMAC-SHA1 according to RFC2104 (http://www.ietf.org/rfc/rfc2104.txt):
+ $hash = base64_encode(
+ pack("H*", sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
+ pack("H*", sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) .
+ $time))))
+ );
+
+ // Store everything in an array. Elsewhere in the code, we'll add the
+ // acutal data before we pass it onto the XML-RPC library:
+ $data['public_key'] = $public_key;
+ $data['time'] = $time;
+ $data['hash'] = $hash;
+
+ return $data;
+ }
+
+
+ function xmlrpc($url) {
+ //require_once './includes/xmlrpc.inc';
+ $args = func_get_args();
+ return call_user_func_array(array('MollomPlugin', '_xmlrpc'), $args);
+ }
+
+ /**
+ * Recursively turn a data structure into objects with 'data' and 'type' attributes.
+ *
+ * @param $data
+ * The data structure.
+ * @param $type
+ * Optional type assign to $data.
+ * @return
+ * Object.
+ */
+ function xmlrpc_value($data, $type = FALSE) {
+ $xmlrpc_value = new stdClass();
+ $xmlrpc_value->data = $data;
+ if (!$type) {
+ $type = $this->xmlrpc_value_calculate_type($xmlrpc_value);
+ }
+ $xmlrpc_value->type = $type;
+ if ($type == 'struct') {
+ // Turn all the values in the array into new xmlrpc_values
+ foreach ($xmlrpc_value->data as $key => $value) {
+ $xmlrpc_value->data[$key] = $this->xmlrpc_value($value);
+ }
+ }
+ if ($type == 'array') {
+ for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) {
+ $xmlrpc_value->data[$i] = $this->xmlrpc_value($xmlrpc_value->data[$i]);
+ }
+ }
+ return $xmlrpc_value;
+ }
+
+ /**
+ * Map PHP type to XML-RPC type.
+ *
+ * @param $xmlrpc_value
+ * Variable whose type should be mapped.
+ * @return
+ * XML-RPC type as string.
+ * @see
+ * http://www.xmlrpc.com/spec#scalars
+ */
+ function xmlrpc_value_calculate_type(&$xmlrpc_value) {
+ // http://www.php.net/gettype: Never use gettype() to test for a certain type [...] Instead, use the is_* functions.
+ if (is_bool($xmlrpc_value->data)) {
+ return 'boolean';
+ }
+ if (is_double($xmlrpc_value->data)) {
+ return 'double';
+ }
+ if (is_int($xmlrpc_value->data)) {
+ return 'int';
+ }
+ if (is_array($xmlrpc_value->data)) {
+ // empty or integer-indexed arrays are 'array', string-indexed arrays 'struct'
+ return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct';
+ }
+ if (is_object($xmlrpc_value->data)) {
+ if ($xmlrpc_value->data->is_date) {
+ return 'date';
+ }
+ if ($xmlrpc_value->data->is_base64) {
+ return 'base64';
+ }
+ $xmlrpc_value->data = get_object_vars($xmlrpc_value->data);
+ return 'struct';
+ }
+ // default
+ return 'string';
+ }
+
+/**
+ * Generate XML representing the given value.
+ *
+ * @param $xmlrpc_value
+ * @return
+ * XML representation of value.
+ */
+function xmlrpc_value_get_xml($xmlrpc_value) {
+ switch ($xmlrpc_value->type) {
+ case 'boolean':
+ return '<boolean>'. (($xmlrpc_value->data) ? '1' : '0') .'</boolean>';
+ break;
+ case 'int':
+ return '<int>'. $xmlrpc_value->data .'</int>';
+ break;
+ case 'double':
+ return '<double>'. $xmlrpc_value->data .'</double>';
+ break;
+ case 'string':
+ // Note: we don't escape apostrophes because of the many blogging clients
+ // that don't support numerical entities (and XML in general) properly.
+ return '<string>'. htmlspecialchars($xmlrpc_value->data) .'</string>';
+ break;
+ case 'array':
+ $return = '<array><data>'."\n";
+ foreach ($xmlrpc_value->data as $item) {
+ $return .= ' <value>'. $this->xmlrpc_value_get_xml($item) ."</value>\n";
+ }
+ $return .= '</data></array>';
+ return $return;
+ break;
+ case 'struct':
+ $return = '<struct>'."\n";
+ foreach ($xmlrpc_value->data as $name => $value) {
+ $return .= " <member><name>". htmlentities($name) ."</name><value>";
+ $return .= $this->xmlrpc_value_get_xml($value) ."</value></member>\n";
+ }
+ $return .= '</struct>';
+ return $return;
+ break;
+ case 'date':
+ return $this->xmlrpc_date_get_xml($xmlrpc_value->data);
+ break;
+ case 'base64':
+ return $this->xmlrpc_base64_get_xml($xmlrpc_value->data);
+ break;
+ }
+ return FALSE;
+}
+
+ /**
+ * Perform an HTTP request.
+ *
+ * This is a flexible and powerful HTTP client implementation. Correctly handles
+ * GET, POST, PUT or any other HTTP requests. Handles redirects.
+ *
+ * @param $url
+ * A string containing a fully qualified URI.
+ * @param $headers
+ * An array containing an HTTP header => value pair.
+ * @param $method
+ * A string defining the HTTP request to use.
+ * @param $data
+ * A string containing data to include in the request.
+ * @param $retry
+ * An integer representing how many times to retry the request in case of a
+ * redirect.
+ * @return
+ * An object containing the HTTP request headers, response code, headers,
+ * data and redirect status.
+ */
+ function http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
+ global $db_prefix;
+
+ $result = new stdClass();
+
+ // Parse the URL and make sure we can handle the schema.
+ $uri = parse_url($url);
+
+ if ($uri == FALSE) {
+ $result->error = 'unable to parse URL';
+ return $result;
+ }
+
+ if (!isset($uri['scheme'])) {
+ $result->error = 'missing schema';
+ return $result;
+ }
+
+ switch ($uri['scheme']) {
+ case 'http':
+ $port = isset($uri['port']) ? $uri['port'] : 80;
+ $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
+ $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
+ break;
+ case 'https':
+ // Note: Only works for PHP 4.3 compiled with OpenSSL.
+ $port = isset($uri['port']) ? $uri['port'] : 443;
+ $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
+ $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
+ break;
+ default:
+ $result->error = 'invalid schema '. $uri['scheme'];
+ return $result;
+ }
+
+ // Make sure the socket opened properly.
+ if (!$fp) {
+ // When a network error occurs, we use a negative number so it does not
+ // clash with the HTTP status codes.
+ $result->code = -$errno;
+ $result->error = trim($errstr);
+
+ // Mark that this request failed. This will trigger a check of the web
+ // server's ability to make outgoing HTTP requests the next time that
+ // requirements checking is performed.
+ // @see system_requirements()
+ //TODO variable_set('drupal_http_request_fails', TRUE);
+
+ return $result;
+ }
+
+ // Construct the path to act on.
+ $path = isset($uri['path']) ? $uri['path'] : '/';
+ if (isset($uri['query'])) {
+ $path .= '?'. $uri['query'];
+ }
+
+ // Create HTTP request.
+ $defaults = array(
+ // RFC 2616: "non-standard ports MUST, default ports MAY be included".
+ // We don't add the port to prevent from breaking rewrite rules checking the
+ // host that do not take into account the port number.
+ 'Host' => "Host: $host",
+ 'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
+ 'Content-Length' => 'Content-Length: '. strlen($data)
+ );
+
+ // If the server url has a user then attempt to use basic authentication
+ if (isset($uri['user'])) {
+ $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
+ }
+
+ // If the database prefix is being used by SimpleTest to run the tests in a copied
+ // database then set the user-agent header to the database prefix so that any
+ // calls to other Drupal pages will run the SimpleTest prefixed database. The
+ // user-agent is used to ensure that multiple testing sessions running at the
+ // same time won't interfere with each other as they would if the database
+ // prefix were stored statically in a file or database variable.
+ if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
+ $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
+ }
+
+ foreach ($headers as $header => $value) {
+ $defaults[$header] = $header .': '. $value;
+ }
+
+ $request = $method .' '. $path ." HTTP/1.0\r\n";
+ $request .= implode("\r\n", $defaults);
+ $request .= "\r\n\r\n";
+ $request .= $data;
+
+ $result->request = $request;
+
+ fwrite($fp, $request);
+
+ // Fetch response.
+ $response = '';
+ while (!feof($fp) && $chunk = fread($fp, 1024)) {
+ $response .= $chunk;
+ }
+ fclose($fp);
+
+ // Parse response.
+ list($split, $result->data) = explode("\r\n\r\n", $response, 2);
+ $split = preg_split("/\r\n|\n|\r/", $split);
+
+ list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
+ $result->headers = array();
+
+ // Parse headers.
+ while ($line = trim(array_shift($split))) {
+ list($header, $value) = explode(':', $line, 2);
+ if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
+ // RFC 2109: the Set-Cookie response header comprises the token Set-
+ // Cookie:, followed by a comma-separated list of one or more cookies.
+ $result->headers[$header] .= ','. trim($value);
+ }
+ else {
+ $result->headers[$header] = trim($value);
+ }
+ }
+
+ $responses = array(
+ 100 => 'Continue', 101 => 'Switching Protocols',
+ 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
+ 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
+ 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
+ );
+ // RFC 2616 states that all unknown HTTP codes must be treated the same as the
+ // base code in their class.
+ if (!isset($responses[$code])) {
+ $code = floor($code / 100) * 100;
+ }
+
+ switch ($code) {
+ case 200: // OK
+ case 304: // Not modified
+ break;
+ case 301: // Moved permanently
+ case 302: // Moved temporarily
+ case 307: // Moved temporarily
+ $location = $result->headers['Location'];
+
+ if ($retry) {
+ $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
+ $result->redirect_code = $result->code;
+ }
+ $result->redirect_url = $location;
+
+ break;
+ default:
+ $result->error = $text;
+ }
+
+ $result->code = $code;
+ return $result;
+ }
+
+ /**
+ * Construct an object representing an XML-RPC message.
+ *
+ * @param $message
+ * String containing XML as defined at http://www.xmlrpc.com/spec
+ * @return
+ * Object
+ */
+ function xmlrpc_message($message) {
+ $xmlrpc_message = new stdClass();
+ $xmlrpc_message->array_structs = array(); // The stack used to keep track of the current array/struct
+ $xmlrpc_message->array_structs_types = array(); // The stack used to keep track of if things are structs or array
+ $xmlrpc_message->current_struct_name = array(); // A stack as well
+ $xmlrpc_message->message = $message;
+ return $xmlrpc_message;
+ }
+
+ /**
+ * Parse an XML-RPC message. If parsing fails, the faultCode and faultString
+ * will be added to the message object.
+ *
+ * @param $xmlrpc_message
+ * Object generated by xmlrpc_message()
+ * @return
+ * TRUE if parsing succeeded; FALSE otherwise
+ */
+ function xmlrpc_message_parse(&$xmlrpc_message) {
+ // First remove the XML declaration
+ $xmlrpc_message->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $xmlrpc_message->message);
+ if (trim($xmlrpc_message->message) == '') {
+ return FALSE;
+ }
+ $xmlrpc_message->_parser = xml_parser_create();
+ // Set XML parser to take the case of tags into account.
+ xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE);
+ // Set XML parser callback functions
+ xml_set_element_handler($xmlrpc_message->_parser, array('MollomPlugin', 'xmlrpc_message_tag_open'), array('MollomPlugin', 'xmlrpc_message_tag_close'));
+ xml_set_character_data_handler($xmlrpc_message->_parser, array('MollomPlugin', 'xmlrpc_message_cdata'));
+ $this->xmlrpc_message_set($xmlrpc_message);
+ if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) {
+ return FALSE;
+ }
+ xml_parser_free($xmlrpc_message->_parser);
+ // Grab the error messages, if any
+ $xmlrpc_message = $this->xmlrpc_message_get();
+ if ($xmlrpc_message->messagetype == 'fault') {
+ $xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode'];
+ $xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString'];
+ }
+ return TRUE;
+ }
+
+ /**
+ * Store a copy of the $xmlrpc_message object temporarily.
+ *
+ * @param $value
+ * Object
+ * @return
+ * The most recently stored $xmlrpc_message
+ */
+ function xmlrpc_message_set($value = NULL) {
+ static $xmlrpc_message;
+ if ($value) {
+ $xmlrpc_message = $value;
+ }
+ return $xmlrpc_message;
+ }
+
+ function xmlrpc_message_get() {
+ return $this->xmlrpc_message_set();
+ }
+
+ function xmlrpc_message_tag_open($parser, $tag, $attr) {
+ $xmlrpc_message = $this->xmlrpc_message_get();
+ $xmlrpc_message->current_tag_contents = '';
+ $xmlrpc_message->last_open = $tag;
+ switch ($tag) {
+ case 'methodCall':
+ case 'methodResponse':
+ case 'fault':
+ $xmlrpc_message->messagetype = $tag;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data':
+ $xmlrpc_message->array_structs_types[] = 'array';
+ $xmlrpc_message->array_structs[] = array();
+ break;
+ case 'struct':
+ $xmlrpc_message->array_structs_types[] = 'struct';
+ $xmlrpc_message->array_structs[] = array();
+ break;
+ }
+ $this->xmlrpc_message_set($xmlrpc_message);
+ }
+
+ function xmlrpc_message_cdata($parser, $cdata) {
+ $xmlrpc_message = $this->xmlrpc_message_get();
+ $xmlrpc_message->current_tag_contents .= $cdata;
+ $this->xmlrpc_message_set($xmlrpc_message);
+ }
+
+ function xmlrpc_message_tag_close($parser, $tag) {
+ $xmlrpc_message = $this->xmlrpc_message_get();
+ $value_flag = FALSE;
+ switch ($tag) {
+ case 'int':
+ case 'i4':
+ $value = (int)trim($xmlrpc_message->current_tag_contents);
+ $value_flag = TRUE;
+ break;
+ case 'double':
+ $value = (double)trim($xmlrpc_message->current_tag_contents);
+ $value_flag = TRUE;
+ break;
+ case 'string':
+ $value = $xmlrpc_message->current_tag_contents;
+ $value_flag = TRUE;
+ break;
+ case 'dateTime.iso8601':
+ $value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents));
+ // $value = $iso->getTimestamp();
+ $value_flag = TRUE;
+ break;
+ case 'value':
+ // If no type is indicated, the type is string
+ // We take special care for empty values
+ if (trim($xmlrpc_message->current_tag_contents) != '' || (isset($xmlrpc_message->last_open) && ($xmlrpc_message->last_open == 'value'))) {
+ $value = (string)$xmlrpc_message->current_tag_contents;
+ $value_flag = TRUE;
+ }
+ unset($xmlrpc_message->last_open);
+ break;
+ case 'boolean':
+ $value = (boolean)trim($xmlrpc_message->current_tag_contents);
+ $value_flag = TRUE;
+ break;
+ case 'base64':
+ $value = base64_decode(trim($xmlrpc_message->current_tag_contents));
+ $value_flag = TRUE;
+ break;
+ // Deal with stacks of arrays and structs
+ case 'data':
+ case 'struct':
+ $value = array_pop($xmlrpc_message->array_structs );
+ array_pop($xmlrpc_message->array_structs_types);
+ $value_flag = TRUE;
+ break;
+ case 'member':
+ array_pop($xmlrpc_message->current_struct_name);
+ break;
+ case 'name':
+ $xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents);
+ break;
+ case 'methodName':
+ $xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents);
+ break;
+ }
+ if ($value_flag) {
+ if (count($xmlrpc_message->array_structs ) > 0) {
+ // Add value to struct or array
+ if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types)-1] == 'struct') {
+ // Add to struct
+ $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name)-1]] = $value;
+ }
+ else {
+ // Add to array
+ $xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][] = $value;
+ }
+ }
+ else {
+ // Just add as a parameter
+ $xmlrpc_message->params[] = $value;
+ }
+ }
+ if (!in_array($tag, array("data", "struct", "member"))) {
+ $xmlrpc_message->current_tag_contents = '';
+ }
+ $this->xmlrpc_message_set($xmlrpc_message);
+ }
+
+ /**
+ * Construct an object representing an XML-RPC request
+ *
+ * @param $method
+ * The name of the method to be called
+ * @param $args
+ * An array of parameters to send with the method.
+ * @return
+ * Object
+ */
+ function xmlrpc_request($method, $args) {
+ $xmlrpc_request = new stdClass();
+ $xmlrpc_request->method = $method;
+ $xmlrpc_request->args = $args;
+ $xmlrpc_request->xml = <<<EOD
+ <?xml version="1.0"?>
+ <methodCall>
+ <methodName>{$xmlrpc_request->method}</methodName>
+ <params>
+
+EOD;
+ foreach ($xmlrpc_request->args as $arg) {
+ $xmlrpc_request->xml .= '<param><value>';
+ $v = $this->xmlrpc_value($arg);
+ $xmlrpc_request->xml .= $this->xmlrpc_value_get_xml($v);
+ $xmlrpc_request->xml .= "</value></param>\n";
+ }
+ $xmlrpc_request->xml .= '</params></methodCall>';
+ return $xmlrpc_request;
+ }
+
+
+ function xmlrpc_error($code = NULL, $message = NULL, $reset = FALSE) {
+ static $xmlrpc_error;
+ if (isset($code)) {
+ $xmlrpc_error = new stdClass();
+ $xmlrpc_error->is_error = TRUE;
+ $xmlrpc_error->code = $code;
+ $xmlrpc_error->message = $message;
+ }
+ elseif ($reset) {
+ $xmlrpc_error = NULL;
+ }
+ return $xmlrpc_error;
+ }
+
+ function xmlrpc_error_get_xml($xmlrpc_error) {
+ return <<<EOD
+ <methodResponse>
+ <fault>
+ <value>
+ <struct>
+ <member>
+ <name>faultCode</name>
+ <value><int>{$xmlrpc_error->code}</int></value>
+ </member>
+ <member>
+ <name>faultString</name>
+ <value><string>{$xmlrpc_error->message}</string></value>
+ </member>
+ </struct>
+ </value>
+ </fault>
+ </methodResponse>
+
+EOD;
+ }
+
+ function xmlrpc_date($time) {
+ $xmlrpc_date = new stdClass();
+ $xmlrpc_date->is_date = TRUE;
+ // $time can be a PHP timestamp or an ISO one
+ if (is_numeric($time)) {
+ $xmlrpc_date->year = gmdate('Y', $time);
+ $xmlrpc_date->month = gmdate('m', $time);
+ $xmlrpc_date->day = gmdate('d', $time);
+ $xmlrpc_date->hour = gmdate('H', $time);
+ $xmlrpc_date->minute = gmdate('i', $time);
+ $xmlrpc_date->second = gmdate('s', $time);
+ $xmlrpc_date->iso8601 = gmdate('Ymd\TH:i:s', $time);
+ }
+ else {
+ $xmlrpc_date->iso8601 = $time;
+ $time = str_replace(array('-', ':'), '', $time);
+ $xmlrpc_date->year = substr($time, 0, 4);
+ $xmlrpc_date->month = substr($time, 4, 2);
+ $xmlrpc_date->day = substr($time, 6, 2);
+ $xmlrpc_date->hour = substr($time, 9, 2);
+ $xmlrpc_date->minute = substr($time, 11, 2);
+ $xmlrpc_date->second = substr($time, 13, 2);
+ }
+ return $xmlrpc_date;
+ }
+
+ function xmlrpc_date_get_xml($xmlrpc_date) {
+ return '<dateTime.iso8601>'. $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day .'T'. $xmlrpc_date->hour .':'. $xmlrpc_date->minute .':'. $xmlrpc_date->second .'</dateTime.iso8601>';
+ }
+
+ function xmlrpc_base64($data) {
+ $xmlrpc_base64 = new stdClass();
+ $xmlrpc_base64->is_base64 = TRUE;
+ $xmlrpc_base64->data = $data;
+ return $xmlrpc_base64;
+ }
+
+ function xmlrpc_base64_get_xml($xmlrpc_base64) {
+ return '<base64>'. base64_encode($xmlrpc_base64->data) .'</base64>';
+ }
+
+ /**
+ * Execute an XML remote procedural call. This is private function; call xmlrpc()
+ * in common.inc instead of this function.
+ *
+ * @return
+ * A $xmlrpc_message object if the call succeeded; FALSE if the call failed
+ */
+ function _xmlrpc() {
+ $args = func_get_args();
+ $url = array_shift($args);
+ $this->xmlrpc_clear_error();
+ if (is_array($args[0])) {
+ $method = 'system.multicall';
+ $multicall_args = array();
+ foreach ($args[0] as $call) {
+ $multicall_args[] = array('methodName' => array_shift($call), 'params' => $call);
+ }
+ $args = array($multicall_args);
+ }
+ else {
+ $method = array_shift($args);
+ }
+ $xmlrpc_request = $this->xmlrpc_request($method, $args);
+ $result = $this->http_request($url, array("Content-Type" => "text/xml"), 'POST', $xmlrpc_request->xml);
+ if ($result->code != 200) {
+ $this->xmlrpc_error($result->code, $result->error);
+ return FALSE;
+ }
+ $message = $this->xmlrpc_message($result->data);
+ // Now parse what we've got back
+ if (!$this->xmlrpc_message_parse($message)) {
+ // XML error
+ $this->xmlrpc_error(-32700, t('Parse error. Not well formed'));
+ return FALSE;
+ }
+ // Is the message a fault?
+ if ($message->messagetype == 'fault') {
+ $this->xmlrpc_error($message->fault_code, $message->fault_string);
+ return FALSE;
+ }
+ // Message must be OK
+ return $message->params[0];
+ }
+
+ /**
+ * Returns the last XML-RPC client error number
+ */
+ function xmlrpc_errno() {
+ $error = $this->xmlrpc_error();
+ return ($error != NULL ? $error->code : NULL);
+ }
+
+ /**
+ * Returns the last XML-RPC client error message
+ */
+ function xmlrpc_error_msg() {
+ $error = xmlrpc_error();
+ return ($error != NULL ? $error->message : NULL);
+ }
+
+ /**
+ * Clears any previous error.
+ */
+ function xmlrpc_clear_error() {
+ $this->xmlrpc_error(NULL, NULL, TRUE);
+ }
+
+}
diff --git a/plugins/Mollom/README b/plugins/Mollom/README
new file mode 100644
index 000000000..2b8c2d8a0
--- /dev/null
+++ b/plugins/Mollom/README
@@ -0,0 +1,22 @@
+The mollom plugin uses mollom.com to filter SN notices for spam.
+
+== Dependencies ==
+Your webserver needs to have xmlrpc php extention loaded.
+This is called php5-xmlrpc in Debian/Ubuntu
+
+== Installation ==
+Add the following to your config.php
+<?php
+addPlugin('Mollom',
+ array(
+ 'public_key' => '...',
+ 'private_key' => '...',
+ 'servers' => array('http://88.151.243.81', 'http://82.103.131.136')
+ )
+);
+
+?>
+
+replace '...' with your own public and private keys for your site, which you can get from mollom.com
+
+If you're using this plugin, i'd love to know about it -- shiny@cpan.org or shiny on freenode.
diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php
index a37d5465e..248afe3fa 100644
--- a/plugins/OpenID/OpenIDPlugin.php
+++ b/plugins/OpenID/OpenIDPlugin.php
@@ -70,7 +70,7 @@ class OpenIDPlugin extends Plugin
$m->connect('index.php?action=finishopenidlogin', array('action' => 'finishopenidlogin'));
$m->connect('index.php?action=finishaddopenid', array('action' => 'finishaddopenid'));
$m->connect('main/openidserver', array('action' => 'openidserver'));
-
+
return true;
}
@@ -101,11 +101,11 @@ class OpenIDPlugin extends Plugin
'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
'version' => '2.0'));
$xrdsOutputter->element('Type', null, 'xri://$xrds*simple');
-
+
//consumer
$xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/return_to',
common_local_url('finishopenidlogin'));
-
+
//provider
$xrdsOutputter->showXrdsService('http://specs.openid.net/auth/2.0/signon',
common_local_url('openidserver'),
@@ -308,4 +308,15 @@ class OpenIDPlugin extends Plugin
$tables[] = 'User_openid_trustroot';
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'OpenID',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Evan Prodromou, Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:OpenID',
+ 'rawdescription' =>
+ _m('Use <a href="http://openid.net/">OpenID</a> to login to the site.'));
+ return true;
+ }
}
diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php
index 44288945b..0b411b8f7 100644
--- a/plugins/OpenID/User_openid_trustroot.php
+++ b/plugins/OpenID/User_openid_trustroot.php
@@ -22,7 +22,7 @@ class User_openid_trustroot extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv);
}
diff --git a/plugins/OpenID/finishopenidlogin.php b/plugins/OpenID/finishopenidlogin.php
index 987fa9213..d25ce696c 100644
--- a/plugins/OpenID/finishopenidlogin.php
+++ b/plugins/OpenID/finishopenidlogin.php
@@ -363,6 +363,7 @@ class FinishopenidloginAction extends Action
if ($url) {
# We don't have to return to it again
common_set_returnto(null);
+ $url = common_inject_session($url);
} else {
$url = common_local_url('all',
array('nickname' =>
diff --git a/plugins/PiwikAnalyticsPlugin.php b/plugins/PiwikAnalyticsPlugin.php
index fefd09867..b353d7255 100644
--- a/plugins/PiwikAnalyticsPlugin.php
+++ b/plugins/PiwikAnalyticsPlugin.php
@@ -97,4 +97,16 @@ ENDOFPIWIK;
$action->inlineScript($piwikCode2);
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'PiwikAnalytics',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Tobias Diekershoff, Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Piwik',
+ 'rawdescription' =>
+ _m('Use <a href="http://piwik.org/">Piwik</a> Open Source Web analytics software.'));
+ return true;
+ }
+
}
diff --git a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php
index 460550518..c59fcca89 100644
--- a/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php
+++ b/plugins/PoweredByStatusNet/PoweredByStatusNetPlugin.php
@@ -31,6 +31,16 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
+/**
+ * Outputs 'powered by StatusNet' after site name
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Sarven Capadisli <csarven@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
class PoweredByStatusNetPlugin extends Plugin
{
function onEndAddressData($action)
@@ -42,4 +52,15 @@ class PoweredByStatusNetPlugin extends Plugin
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'PoweredByStatusNet',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Sarven Capadisli',
+ 'homepage' => 'http://status.net/wiki/Plugin:PoweredByStatusNet',
+ 'rawdescription' =>
+ _m('Outputs powered by <a href="http://status.net/">StatusNet</a> after site name.'));
+ return true;
+ }
}
diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php
index 76a438dd5..ddba942e6 100644
--- a/plugins/PtitUrl/PtitUrlPlugin.php
+++ b/plugins/PtitUrl/PtitUrlPlugin.php
@@ -56,5 +56,18 @@ class PtitUrlPlugin extends UrlShortenerPlugin
return strval($xml['href']);
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => sprintf('PtitUrl (%s)', $this->shortenerName),
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:PtitUrl',
+ 'rawdescription' =>
+ sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
+ $this->shortenerName));
+
+ return true;
+ }
}
diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
index d15a869cb..c40d906a5 100644
--- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php
+++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php
@@ -118,4 +118,16 @@ class PubSubHubBubPlugin extends Plugin
}
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'PubSubHubBub',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:PubSubHubBub',
+ 'rawdescription' =>
+ _m('The PubSubHubBub plugin pushes RSS/Atom updates to a <a href="http://pubsubhubbub.googlecode.com/">PubSubHubBub</a> hub.'));
+
+ return true;
+ }
}
diff --git a/plugins/RSSCloud/LoggingAggregator.php b/plugins/RSSCloud/LoggingAggregator.php
new file mode 100644
index 000000000..e37eed16a
--- /dev/null
+++ b/plugins/RSSCloud/LoggingAggregator.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * This test class pretends to be an RSS aggregator. It logs notifications
+ * from the cloud.
+ *
+ * PHP version 5
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Dummy aggregator that acts as a proper notification handler. It
+ * doesn't do anything but respond correctly when notified via
+ * REST. Mostly, this is just and action I used to develop the plugin
+ * and easily test things end-to-end. I'm leaving it in here as it
+ * may be useful for developing the plugin further.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+class LoggingAggregatorAction extends Action
+{
+
+ var $challenge = null;
+ var $url = null;
+
+ /**
+ * Initialization.
+ *
+ * @param array $args Web and URL arguments
+ *
+ * @return boolean false if user doesn't exist
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->url = $this->arg('url');
+ $this->challenge = $this->arg('challenge');
+
+ common_debug("args = " . var_export($this->args, true));
+ common_debug('url = ' . $this->url . ' challenge = ' . $this->challenge);
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (empty($this->url)) {
+ $this->showError('Hey, you have to provide a url parameter.');
+ return;
+ }
+
+ if (!empty($this->challenge)) {
+
+ // must be a GET
+
+ if ($_SERVER['REQUEST_METHOD'] != 'GET') {
+ $this->showError('This resource requires an HTTP GET.');
+ return;
+ }
+
+ header('Content-Type: text/xml');
+ echo $this->challenge;
+
+ } else {
+
+ // must be a POST
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->showError('This resource requires an HTTP POST.');
+ return;
+ }
+
+ header('Content-Type: text/xml');
+ Echo "<notifyResult success='true' msg='Thanks for the update.' />\n";
+ }
+
+ $this->ip = $_SERVER['REMOTE_ADDR'];
+
+ common_log(LOG_INFO, 'RSSCloud Logging Aggregator - ' .
+ $this->ip . ' claims the feed at ' .
+ $this->url . ' has been updated.');
+ }
+
+ /**
+ * Show an XML error when things go badly
+ *
+ * @param string $msg the error message
+ *
+ * @return void
+ */
+
+ function showError($msg)
+ {
+ header('HTTP/1.1 400 Bad Request');
+ header('Content-Type: text/xml');
+ echo "<?xml version='1.0'?>\n";
+ echo "<notifyResult success='false' msg='$msg' />\n";
+ }
+
+} \ No newline at end of file
diff --git a/plugins/RSSCloud/README b/plugins/RSSCloud/README
new file mode 100644
index 000000000..1237e3e0e
--- /dev/null
+++ b/plugins/RSSCloud/README
@@ -0,0 +1,54 @@
+This plugin enables RSSCloud (http://rsscloud.org/) publishing and
+subscription handling for RSS 2.0 profile feeds (i.e:
+http://SITE/PATH/api/statuses/user_timeline/USERNAME.rss). When the
+plugin is enabled, StatusNet acts as both the publisher and hub ('writer' and
+'cloud' in RSSCloud parlance), but only for local StatusNet feeds. It's
+not possible to use it as a general purpose hub -- for instance you can't
+subscribe and get updates to a Wordpress feed from StatusNet using this
+plugin.
+
+To use the plugin, add the following to your config.php:
+
+ addPlugin('RSSCloud');
+
+Enabling the plugin will add a <cloud> element to your RSS 2.0 profile feeds
+that looks like this:
+
+ <cloud domain="SITE" port="80" path="/main/rsscloud/request_notify"
+ registerProcedure="" protocol="http-post"/>
+
+Aggregators may subscribe by sending a proper REST RSSCloud subscription
+request (the optional 'domain' parameter with challenge is supported).
+Subscribing aggregators will be notified ('pinged') when users they have
+subscribed to post new notices. Currently, REST is the only protocol
+supported for notifications.
+
+Deamon
+------
+
+There's also a daemon for offline processing of queued notices with
+RSSCloud destinations, which will start automatically if/when you run
+scripts/startdaemons.sh.
+
+Notes
+-----
+
+- Again, only RSS 2.0 profile feeds may be subscribed to, and they have
+ to be the ones with user names in them, like:
+ http://SITE/PATH/api/statuses/user_timeline/USERNAME.rss
+- Subscriptions are deleted after three notification failures in a row
+ (not sure this is optimal).
+- The plugin includes a dummy LoggingAggregator class that can be used
+ for end-to-end testing. You probably don't want to mess with it.
+
+TODO
+----
+
+- Figure out why the RSSCloudSubcription can't ->delete() or ->update()
+- Support pinging via XML-RPC and SOAP
+- Automatically delete subscriptions? Point of reference: Dave's hub
+ implementation auto-deletes them after 25 hours. WordPress never deletes them.
+- Support additional feed URL addresses for the same feed (e.g.: by numeric ID,
+ ?user_id=xxx, etc.)
+- Support additional feeds that make sense (e.g: replies)?
+- Possibly use "rssCloud" (like Dave) instead of "RSSCloud" everywhere
diff --git a/plugins/RSSCloud/RSSCloudNotifier.php b/plugins/RSSCloud/RSSCloudNotifier.php
new file mode 100644
index 000000000..d454691c8
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudNotifier.php
@@ -0,0 +1,240 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class to ping an rssCloud endpoint when a feed has been updated
+ *
+ * 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 Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Class for notifying cloud-enabled RSS aggregators that StatusNet
+ * feeds have been updated.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+class RSSCloudNotifier
+{
+ const MAX_FAILURES = 3;
+
+ /**
+ * Send an HTTP GET to the notification handler with a
+ * challenge string to see if it repsonds correctly.
+ *
+ * @param string $endpoint URL of the notification handler
+ * @param string $feed the feed being subscribed to
+ *
+ * @return boolean success
+ */
+ function challenge($endpoint, $feed)
+ {
+ $code = common_confirmation_code(128);
+ $params = array('url' => $feed, 'challenge' => $code);
+ $url = $endpoint . '?' . http_build_query($params);
+
+ try {
+ $client = new HTTPClient();
+ $response = $client->get($url);
+ } catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_INFO,
+ 'RSSCloud plugin - failure testing notify handler ' .
+ $endpoint . ' - ' . $e->getMessage());
+ return false;
+ }
+
+ // Check response is betweet 200 and 299 and body contains challenge data
+
+ $status = $response->getStatus();
+ $body = $response->getBody();
+
+ if ($status >= 200 && $status < 300) {
+
+ // NOTE: the spec says that the body must contain the string
+ // challenge. It doesn't say that the body must contain the
+ // challenge string ONLY, although that seems to be the way
+ // the other implementors have interpreted it.
+
+ if (strpos($body, $code) !== false) {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ "success testing notify handler: $endpoint");
+ return true;
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ 'challenge/repsonse failed for notify handler ' .
+ $endpoint);
+ common_debug('body = ' . var_export($body, true));
+ return false;
+ }
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ "failure testing notify handler: $endpoint " .
+ ' - got HTTP ' . $status);
+ common_debug('body = ' . var_export($body, true));
+ return false;
+ }
+ }
+
+ /**
+ * HTTP POST a notification that a feed has been updated
+ * ('ping the cloud').
+ *
+ * @param String $endpoint URL of the notification handler
+ * @param String $feed the feed being subscribed to
+ *
+ * @return boolean success
+ */
+ function postUpdate($endpoint, $feed)
+ {
+
+ $headers = array();
+ $postdata = array('url' => $feed);
+
+ try {
+ $client = new HTTPClient();
+ $response = $client->post($endpoint, $headers, $postdata);
+ } catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
+ $endpoint . ' that feed ' . $feed .
+ ' has changed: ' . $e->getMessage());
+ return false;
+ }
+
+ $status = $response->getStatus();
+
+ if ($status >= 200 && $status < 300) {
+ common_log(LOG_INFO, 'RSSCloud plugin - success notifying ' .
+ $endpoint . ' that feed ' . $feed . ' has changed.');
+ return true;
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
+ $endpoint . ' that feed ' . $feed .
+ ' has changed: got HTTP ' . $status);
+ return false;
+ }
+ }
+
+ /**
+ * Notify all subscribers to a profile feed that it has changed.
+ *
+ * @param Profile $profile the profile whose feed has been
+ * updated
+ *
+ * @return boolean success
+ */
+ function notify($profile)
+ {
+ $feed = common_path('api/statuses/user_timeline/') .
+ $profile->nickname . '.rss';
+
+ $cloudSub = new RSSCloudSubscription();
+
+ $cloudSub->subscribed = $profile->id;
+
+ if ($cloudSub->find()) {
+ while ($cloudSub->fetch()) {
+ $result = $this->postUpdate($cloudSub->url, $feed);
+ if ($result == false) {
+ $this->handleFailure($cloudSub);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle problems posting cloud notifications. Increment the failure
+ * count, or delete the subscription if the maximum number of failures
+ * is exceeded.
+ *
+ * XXX: Redo with proper DB_DataObject methods once I figure out what
+ * what the problem is with pluginized DB_DataObjects. -Z
+ *
+ * @param RSSCloudSubscription $cloudSub the subscription in question
+ *
+ * @return boolean success
+ */
+ function handleFailure($cloudSub)
+ {
+ $failCnt = $cloudSub->failures + 1;
+
+ if ($failCnt == self::MAX_FAILURES) {
+
+ common_log(LOG_INFO,
+ 'Deleting RSSCloud subcription ' .
+ '(max failure count reached), profile: ' .
+ $cloudSub->subscribed .
+ ' handler: ' .
+ $cloudSub->url);
+
+ // XXX: WTF! ->delete() doesn't work. Clearly, there are some issues with
+ // the DB_DataObject, or my understanding of it. Have to drop into SQL.
+
+ // $result = $cloudSub->delete();
+
+ $qry = 'DELETE from rsscloud_subscription' .
+ ' WHERE subscribed = ' . $cloudSub->subscribed .
+ ' AND url = \'' . $cloudSub->url . '\'';
+
+ $result = $cloudSub->query($qry);
+
+ if (!$result) {
+ common_log_db_error($cloudSub, 'DELETE', __FILE__);
+ common_log(LOG_ERR, 'Could not delete RSSCloud subscription.');
+ }
+
+ } else {
+
+ common_debug('Updating failure count on RSSCloud subscription. ' .
+ $failCnt);
+
+ $failCnt = $cloudSub->failures + 1;
+
+ // XXX: ->update() not working either, gar!
+
+ $qry = 'UPDATE rsscloud_subscription' .
+ ' SET failures = ' . $failCnt .
+ ' WHERE subscribed = ' . $cloudSub->subscribed .
+ ' AND url = \'' . $cloudSub->url . '\'';
+
+ $result = $cloudSub->query($qry);
+
+ if (!$result) {
+ common_log_db_error($cloudsub, 'UPDATE', __FILE__);
+ common_log(LOG_ERR,
+ 'Could not update failure ' .
+ 'count on RSSCloud subscription');
+ }
+ }
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php
new file mode 100644
index 000000000..2de162628
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudPlugin.php
@@ -0,0 +1,295 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to support RSSCloud
+ *
+ * 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 Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+define('RSSCLOUDPLUGIN_VERSION', '0.1');
+
+/**
+ * Plugin class for adding RSSCloud capabilities to StatusNet
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+
+class RSSCloudPlugin extends Plugin
+{
+ /**
+ * Our friend, the constructor
+ *
+ * @return void
+ */
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Setup the info for the subscription handler. Allow overriding
+ * to point at another cloud hub (not currently used).
+ *
+ * @return void
+ */
+
+ function onInitializePlugin()
+ {
+ $this->domain = common_config('rsscloud', 'domain');
+ $this->port = common_config('rsscloud', 'port');
+ $this->path = common_config('rsscloud', 'path');
+ $this->funct = common_config('rsscloud', 'function');
+ $this->protocol = common_config('rsscloud', 'protocol');
+
+ // set defaults
+
+ $local_server = parse_url(common_path('main/rsscloud/request_notify'));
+
+ if (empty($this->domain)) {
+ $this->domain = $local_server['host'];
+ }
+
+ if (empty($this->port)) {
+ $this->port = '80';
+ }
+
+ if (empty($this->path)) {
+ $this->path = $local_server['path'];
+ }
+
+ if (empty($this->funct)) {
+ $this->funct = '';
+ }
+
+ if (empty($this->protocol)) {
+ $this->protocol = 'http-post';
+ }
+ }
+
+ /**
+ * Add RSSCloud-related paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @param Mapper &$m URL parser and mapper
+ *
+ * @return boolean hook return
+ */
+
+ function onRouterInitialized(&$m)
+ {
+ $m->connect('/main/rsscloud/request_notify',
+ array('action' => 'RSSCloudRequestNotify'));
+
+ // XXX: This is just for end-to-end testing. Uncomment if you need to pretend
+ // to be a cloud hub for some reason.
+ //$m->connect('/main/rsscloud/notify',
+ // array('action' => 'LoggingAggregator'));
+
+ return true;
+ }
+
+ /**
+ * Automatically load the actions and libraries used by
+ * the RSSCloud plugin
+ *
+ * @param Class $cls the class
+ *
+ * @return boolean hook return
+ *
+ */
+
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'RSSCloudSubscription':
+ include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudSubscription.php';
+ return false;
+ case 'RSSCloudNotifier':
+ include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php';
+ return false;
+ case 'RSSCloudRequestNotifyAction':
+ case 'LoggingAggregatorAction':
+ include_once INSTALLDIR . '/plugins/RSSCloud/' .
+ mb_substr($cls, 0, -6) . '.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Add a <cloud> element to the RSS feed (after the rss <channel>
+ * element is started).
+ *
+ * @param Action $action the ApiAction
+ *
+ * @return void
+ */
+
+ function onStartApiRss($action)
+ {
+ if (get_class($action) == 'ApiTimelineUserAction') {
+
+ $attrs = array('domain' => $this->domain,
+ 'port' => $this->port,
+ 'path' => $this->path,
+ 'registerProcedure' => $this->funct,
+ 'protocol' => $this->protocol);
+
+ // Dipping into XMLWriter to avoid a full end element (</cloud>).
+
+ $action->xw->startElement('cloud');
+ foreach ($attrs as $name => $value) {
+ $action->xw->writeAttribute($name, $value);
+ }
+
+ $action->xw->endElement();
+ }
+ }
+
+ /**
+ * Add an RSSCloud queue item for each notice
+ *
+ * @param Notice $notice the notice
+ * @param array &$transports the list of transports (queues)
+ *
+ * @return boolean hook return
+ */
+
+ function onStartEnqueueNotice($notice, &$transports)
+ {
+ array_push($transports, 'rsscloud');
+ return true;
+ }
+
+ /**
+ * broadcast the message when not using queuehandler
+ *
+ * @param Notice &$notice the notice
+ * @param array $queue destination queue
+ *
+ * @return boolean hook return
+ */
+
+ function onUnqueueHandleNotice(&$notice, $queue)
+ {
+ if (($queue == 'rsscloud') && ($this->_isLocal($notice))) {
+
+ common_debug('broadcasting rssCloud bound notice ' . $notice->id);
+
+ $profile = $notice->getProfile();
+
+ $notifier = new RSSCloudNotifier();
+ $notifier->notify($profile);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine whether the notice was locally created
+ *
+ * @param Notice $notice the notice in question
+ *
+ * @return boolean locality
+ */
+
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
+ }
+
+ /**
+ * Create the rsscloud_subscription table if it's not
+ * already in the DB
+ *
+ * @return boolean hook return
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+ $schema->ensureTable('rsscloud_subscription',
+ array(new ColumnDef('subscribed', 'integer',
+ null, false, 'PRI'),
+ new ColumnDef('url', 'varchar',
+ '255', false, 'PRI'),
+ new ColumnDef('failures', 'integer',
+ null, false, null, 0),
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'timestamp',
+ null, false, null,
+ 'CURRENT_TIMESTAMP',
+ 'on update CURRENT_TIMESTAMP')
+ ));
+ return true;
+ }
+
+ /**
+ * Add RSSCloudQueueHandler to the list of valid daemons to
+ * start
+ *
+ * @param array $daemons the list of daemons to run
+ *
+ * @return boolean hook return
+ *
+ */
+
+ function onGetValidDaemons($daemons)
+ {
+ array_push($daemons, INSTALLDIR .
+ '/plugins/RSSCloud/RSSCloudQueueHandler.php');
+ return true;
+ }
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'RSSCloud',
+ 'version' => RSSCLOUDPLUGIN_VERSION,
+ 'author' => 'Zach Copley',
+ 'homepage' => 'http://status.net/wiki/Plugin:RSSCloud',
+ 'rawdescription' =>
+ _m('The RSSCloud plugin enables your StatusNet instance to publish ' .
+ 'real-time updates for profile RSS feeds using the ' .
+ '<a href="http://rsscloud.org/">RSSCloud protocol</a>".'));
+
+ return true;
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php
new file mode 100755
index 000000000..693dd27c1
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudQueueHandler.php
@@ -0,0 +1,78 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for pushing new notices to RSSCloud subscribers.
+
+ -i --id Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+require_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php';
+require_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudSubscription.php';
+
+class RSSCloudQueueHandler extends QueueHandler
+{
+ var $notifier = null;
+
+ function transport()
+ {
+ return 'rsscloud';
+ }
+
+ function start()
+ {
+ $this->log(LOG_INFO, "INITIALIZE");
+ $this->notifier = new RSSCloudNotifier();
+ return true;
+ }
+
+ function handle_notice($notice)
+ {
+ $profile = $notice->getProfile();
+ return $this->notifier->notify($profile);
+ }
+
+ function finish()
+ {
+ }
+
+}
+
+if (have_option('i')) {
+ $id = get_option_value('i');
+} else if (have_option('--id')) {
+ $id = get_option_value('--id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+$handler = new RSSCloudQueueHandler($id);
+
+$handler->runOnce();
diff --git a/plugins/RSSCloud/RSSCloudRequestNotify.php b/plugins/RSSCloud/RSSCloudRequestNotify.php
new file mode 100644
index 000000000..d76c08d37
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudRequestNotify.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * Action to let RSSCloud aggregators request update notification when
+ * user profile feeds change.
+ *
+ * PHP version 5
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Action class to handle RSSCloud notification (subscription) requests
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+
+class RSSCloudRequestNotifyAction extends Action
+{
+ /**
+ * Initialization.
+ *
+ * @param array $args Web and URL arguments
+ *
+ * @return boolean false if user doesn't exist
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->ip = $_SERVER['REMOTE_ADDR'];
+ $this->port = $this->arg('port');
+ $this->path = $this->arg('path');
+
+ if ($this->path[0] != '/') {
+ $this->path = '/' . $this->path;
+ }
+
+ $this->protocol = $this->arg('protocol');
+ $this->procedure = $this->arg('notifyProcedure');
+ $this->domain = $this->arg('domain');
+
+ $this->feeds = $this->getFeeds();
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * Checks for all the required parameters for a subscription,
+ * validates that the feed being subscribed to is real, and then
+ * saves the subsctiption.
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->showResult(false, 'Request must be POST.');
+ return;
+ }
+
+ $missing = array();
+
+ if (empty($this->port)) {
+ $missing[] = 'port';
+ }
+
+ if (empty($this->path)) {
+ $missing[] = 'path';
+ }
+
+ if (empty($this->protocol)) {
+ $missing[] = 'protocol';
+ } else if (strtolower($this->protocol) != 'http-post') {
+ $msg = 'Only http-post notifications are supported at this time.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (!isset($this->procedure)) {
+ $missing[] = 'notifyProcedure';
+ }
+
+ if (!empty($missing)) {
+ $msg = 'The following parameters were missing from the request body: ' .
+ implode(', ', $missing) . '.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (empty($this->feeds)) {
+ $msg = 'You must provide at least one valid profile feed url ' .
+ '(url1, url2, url3 ... urlN).';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ // We have to validate everything before saving anything.
+ // We only return one success or failure no matter how
+ // many feeds the subscriber is trying to subscribe to
+
+ foreach ($this->feeds as $feed) {
+
+ if (!$this->validateFeed($feed)) {
+
+ $nh = $this->getNotifyUrl();
+ common_log(LOG_WARNING,
+ "RSSCloud plugin - $nh tried to subscribe to invalid feed: $feed");
+
+ $msg = 'Feed subscription failed - Not a valid feed.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (!$this->testNotificationHandler($feed)) {
+ $msg = 'Feed subscription failed - ' .
+ 'notification handler doesn\'t respond correctly.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ }
+
+ foreach ($this->feeds as $feed) {
+ $this->saveSubscription($feed);
+ }
+
+ // XXX: What to do about deleting stale subscriptions?
+ // 25 hours seems harsh. WordPress doesn't ever remove
+ // subscriptions.
+
+ $msg = 'Thanks for the subscription. ' .
+ 'When the feed(s) update(s) we\'ll notify you.';
+
+ $this->showResult(true, $msg);
+ }
+
+ /**
+ * Validate that the requested feed is one we serve
+ * up via RSSCloud.
+ *
+ * @param string $feed the feed in question
+ *
+ * @return void
+ */
+
+ function validateFeed($feed)
+ {
+ $user = $this->userFromFeed($feed);
+
+ if (empty($user)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Pull all of the urls (url1, url2, url3...urlN) that
+ * the subscriber wants to subscribe to.
+ *
+ * @return array $feeds the list of feeds
+ */
+
+ function getFeeds()
+ {
+ $feeds = array();
+
+ while (list($key, $feed) = each($this->args)) {
+ if (preg_match('/^url\d*$/', $key)) {
+ $feeds[] = $feed;
+ }
+ }
+
+ return $feeds;
+ }
+
+ /**
+ * Test that a notification handler is there and is reponding
+ * correctly. This is called before adding a subscription.
+ *
+ * @param string $feed the feed to verify
+ *
+ * @return boolean success result
+ */
+
+ function testNotificationHandler($feed)
+ {
+ $notifyUrl = $this->getNotifyUrl();
+
+ $notifier = new RSSCloudNotifier();
+
+ if (isset($this->domain)) {
+
+ // 'domain' param set, so we have to use GET and send a challenge
+
+ common_log(LOG_INFO,
+ 'RSSCloud plugin - Testing notification handler with challenge: ' .
+ $notifyUrl);
+ return $notifier->challenge($notifyUrl, $feed);
+
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - Testing notification handler: ' .
+ $notifyUrl);
+
+ return $notifier->postUpdate($notifyUrl, $feed);
+ }
+ }
+
+ /**
+ * Build the URL for the notification handler based on the
+ * parameters passed in with the subscription request.
+ *
+ * @return string notification handler url
+ */
+
+ function getNotifyUrl()
+ {
+ if (isset($this->domain)) {
+ return 'http://' . $this->domain . ':' . $this->port . $this->path;
+ } else {
+ return 'http://' . $this->ip . ':' . $this->port . $this->path;
+ }
+ }
+
+ /**
+ * Uses the nickname part of the subscribed feed URL to figure out
+ * whethere there's really a user with such a feed. Used to
+ * validate feeds before adding a subscription.
+ *
+ * @param string $feed the feed in question
+ *
+ * @return boolean success
+ */
+
+ function userFromFeed($feed)
+ {
+ // We only do profile feeds
+
+ $path = common_path('api/statuses/user_timeline/');
+ $valid = '%^' . $path . '(?<nickname>.*)\.rss$%';
+
+ if (preg_match($valid, $feed, $matches)) {
+ $user = User::staticGet('nickname', $matches['nickname']);
+ if (!empty($user)) {
+ return $user;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Save an RSSCloud subscription
+ *
+ * @param string $feed a valid profile feed
+ *
+ * @return boolean success result
+ */
+
+ function saveSubscription($feed)
+ {
+ $user = $this->userFromFeed($feed);
+
+ $notifyUrl = $this->getNotifyUrl();
+
+ $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);
+
+ if ($sub) {
+ common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl refreshed subscription" .
+ " to user $user->nickname (id: $user->id).");
+ } else {
+
+ $sub = new RSSCloudSubscription();
+
+ $sub->subscribed = $user->id;
+ $sub->url = $notifyUrl;
+ $sub->created = common_sql_now();
+
+ if (!$sub->insert()) {
+ common_log_db_error($sub, 'INSERT', __FILE__);
+ return false;
+ }
+
+ common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl subscribed" .
+ " to user $user->nickname (id: $user->id)");
+ }
+
+ return true;
+ }
+
+ /**
+ * Show an XML message indicating the subscription
+ * was successful or failed.
+ *
+ * @param boolean $success whether it was good or bad
+ * @param string $msg the message to output
+ *
+ * @return boolean success result
+ */
+
+ function showResult($success, $msg)
+ {
+ $this->startXML();
+ $this->elementStart('notifyResult',
+ array('success' => ($success) ? 'true' : 'false',
+ 'msg' => $msg));
+ $this->endXML();
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudSubscription.php b/plugins/RSSCloud/RSSCloudSubscription.php
new file mode 100644
index 000000000..396c604e7
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudSubscription.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Table Definition for rsscloud_subscription
+ */
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class RSSCloudSubscription extends Memcached_DataObject {
+
+ var $__table='rsscloud_subscription'; // table name
+ var $subscribed; // int primary key user id
+ var $url; // string primary key
+ var $failures; // int
+ var $created; // datestamp()
+ var $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('DataObjects_Grp',$k,$v); }
+
+ function table()
+ {
+
+ $db = $this->getDatabaseConnection();
+ $dbtype = $db->phptype;
+
+ $cols = array('subscribed' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'url' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'failures' => DB_DATAOBJECT_INT,
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => ($dbtype == 'mysql' || $dbtype == 'mysqli') ?
+ DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL :
+ DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME
+ );
+
+ return $cols;
+ }
+
+ function keys()
+ {
+ return array('subscribed' => 'N', 'url' => 'N');
+ }
+
+ static function getSubscription($subscribed, $url)
+ {
+ $sub = new RSSCloudSubscription();
+ $sub->whereAdd("subscribed = $subscribed");
+ $sub->whereAdd("url = '$url'");
+ $sub->limit(1);
+
+ if ($sub->find()) {
+ $sub->fetch();
+ return $sub;
+ }
+
+ return false;
+ }
+
+}
diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php
index a810b7165..21e465b53 100644
--- a/plugins/Realtime/RealtimePlugin.php
+++ b/plugins/Realtime/RealtimePlugin.php
@@ -310,8 +310,7 @@ class RealtimePlugin extends Plugin
function _getScripts()
{
- return array('plugins/Realtime/realtimeupdate.js',
- 'plugins/Realtime/json2.js');
+ return array('plugins/Realtime/realtimeupdate.js');
}
function _updateInitialize($timeline, $user_id)
diff --git a/plugins/Realtime/json2.js b/plugins/Realtime/json2.js
deleted file mode 100644
index 7e27df518..000000000
--- a/plugins/Realtime/json2.js
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- http://www.JSON.org/json2.js
- 2009-04-16
-
- Public Domain.
-
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
-
- See http://www.JSON.org/js.html
-
- This file creates a global JSON object containing two methods: stringify
- and parse.
-
- JSON.stringify(value, replacer, space)
- value any JavaScript value, usually an object or array.
-
- replacer an optional parameter that determines how object
- values are stringified for objects. It can be a
- function or an array of strings.
-
- space an optional parameter that specifies the indentation
- of nested structures. If it is omitted, the text will
- be packed without extra whitespace. If it is a number,
- it will specify the number of spaces to indent at each
- level. If it is a string (such as '\t' or '&nbsp;'),
- it contains the characters used to indent at each level.
-
- This method produces a JSON text from a JavaScript value.
-
- When an object value is found, if the object contains a toJSON
- method, its toJSON method will be called and the result will be
- stringified. A toJSON method does not serialize: it returns the
- value represented by the name/value pair that should be serialized,
- or undefined if nothing should be serialized. The toJSON method
- will be passed the key associated with the value, and this will be
- bound to the object holding the key.
-
- For example, this would serialize Dates as ISO strings.
-
- Date.prototype.toJSON = function (key) {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- You can provide an optional replacer method. It will be passed the
- key and value of each member, with this bound to the containing
- object. The value that is returned from your method will be
- serialized. If your method returns undefined, then the member will
- be excluded from the serialization.
-
- If the replacer parameter is an array of strings, then it will be
- used to select the members to be serialized. It filters the results
- such that only members with keys listed in the replacer array are
- stringified.
-
- Values that do not have JSON representations, such as undefined or
- functions, will not be serialized. Such values in objects will be
- dropped; in arrays they will be replaced with null. You can use
- a replacer function to replace those with JSON values.
- JSON.stringify(undefined) returns undefined.
-
- The optional space parameter produces a stringification of the
- value that is filled with line breaks and indentation to make it
- easier to read.
-
- If the space parameter is a non-empty string, then that string will
- be used for indentation. If the space parameter is a number, then
- the indentation will be that many spaces.
-
- Example:
-
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
- // text is '["e",{"pluribus":"unum"}]'
-
-
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
-
- text = JSON.stringify([new Date()], function (key, value) {
- return this[key] instanceof Date ?
- 'Date(' + this[key] + ')' : value;
- });
- // text is '["Date(---current time---)"]'
-
-
- JSON.parse(text, reviver)
- This method parses a JSON text to produce an object or array.
- It can throw a SyntaxError exception.
-
- The optional reviver parameter is a function that can filter and
- transform the results. It receives each of the keys and values,
- and its return value is used instead of the original value.
- If it returns what it received, then the structure is not modified.
- If it returns undefined then the member is deleted.
-
- Example:
-
- // Parse the text. Values that look like ISO date strings will
- // be converted to Date objects.
-
- myData = JSON.parse(text, function (key, value) {
- var a;
- if (typeof value === 'string') {
- a =
-/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
- if (a) {
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
- +a[5], +a[6]));
- }
- }
- return value;
- });
-
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
- var d;
- if (typeof value === 'string' &&
- value.slice(0, 5) === 'Date(' &&
- value.slice(-1) === ')') {
- d = new Date(value.slice(5, -1));
- if (d) {
- return d;
- }
- }
- return value;
- });
-
-
- This is a reference implementation. You are free to copy, modify, or
- redistribute.
-
- This code should be minified before deployment.
- See http://javascript.crockford.com/jsmin.html
-
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
- NOT CONTROL.
-*/
-
-/*jslint evil: true */
-
-/*global JSON */
-
-/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
- call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
- getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
- lastIndex, length, parse, prototype, push, replace, slice, stringify,
- test, toJSON, toString, valueOf
-*/
-
-// Create a JSON object only if one does not already exist. We create the
-// methods in a closure to avoid creating global variables.
-
-if (!this.JSON) {
- JSON = {};
-}
-(function () {
-
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- if (typeof Date.prototype.toJSON !== 'function') {
-
- Date.prototype.toJSON = function (key) {
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- String.prototype.toJSON =
- Number.prototype.toJSON =
- Boolean.prototype.toJSON = function (key) {
- return this.valueOf();
- };
- }
-
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
-
-
- function quote(string) {
-
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can safely slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe escape
-// sequences.
-
- escapable.lastIndex = 0;
- return escapable.test(string) ?
- '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === 'string' ? c :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- }
-
-
- function str(key, holder) {
-
-// Produce a string from holder[key].
-
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
-
-// If the value has a toJSON method, call it to obtain a replacement value.
-
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
-// If we were called with a replacer function, then call the replacer to
-// obtain a replacement value.
-
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
-
-// What happens next depends on the value's type.
-
- switch (typeof value) {
- case 'string':
- return quote(value);
-
- case 'number':
-
-// JSON numbers must be finite. Encode non-finite numbers as null.
-
- return isFinite(value) ? String(value) : 'null';
-
- case 'boolean':
- case 'null':
-
-// If the value is a boolean or null, convert it to a string. Note:
-// typeof null does not produce 'null'. The case is included here in
-// the remote chance that this gets fixed someday.
-
- return String(value);
-
-// If the type is 'object', we might be dealing with an object or an array or
-// null.
-
- case 'object':
-
-// Due to a specification blunder in ECMAScript, typeof null is 'object',
-// so watch out for that case.
-
- if (!value) {
- return 'null';
- }
-
-// Make an array to hold the partial results of stringifying this object value.
-
- gap += indent;
- partial = [];
-
-// Is the value an array?
-
- if (Object.prototype.toString.apply(value) === '[object Array]') {
-
-// The value is an array. Stringify every element. Use null as a placeholder
-// for non-JSON values.
-
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
-
-// Join all of the elements together, separated with commas, and wrap them in
-// brackets.
-
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
-
-// If the replacer is an array, use it to select the members to be stringified.
-
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- k = rep[i];
- if (typeof k === 'string') {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
-
-// Otherwise, iterate through all of the keys in the object.
-
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
-
-// Join all of the member texts together, separated with commas,
-// and wrap them in braces.
-
- v = partial.length === 0 ? '{}' :
- gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- }
-
-// If the JSON object does not yet have a stringify method, give it one.
-
- if (typeof JSON.stringify !== 'function') {
- JSON.stringify = function (value, replacer, space) {
-
-// The stringify method takes a value and an optional replacer, and an optional
-// space parameter, and returns a JSON text. The replacer can be a function
-// that can replace values, or an array of strings that will select the keys.
-// A default replacer method can be provided. Use of the space parameter can
-// produce text that is more easily readable.
-
- var i;
- gap = '';
- indent = '';
-
-// If the space parameter is a number, make an indent string containing that
-// many spaces.
-
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
-
-// If the space parameter is a string, it will be used as the indent string.
-
- } else if (typeof space === 'string') {
- indent = space;
- }
-
-// If there is a replacer, it must be a function or an array.
-// Otherwise, throw an error.
-
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
-
-// Make a fake root object containing our value under the key of ''.
-// Return the result of stringifying the value.
-
- return str('', {'': value});
- };
- }
-
-
-// If the JSON object does not yet have a parse method, give it one.
-
- if (typeof JSON.parse !== 'function') {
- JSON.parse = function (text, reviver) {
-
-// The parse method takes a text and an optional reviver function, and returns
-// a JavaScript value if the text is a valid JSON text.
-
- var j;
-
- function walk(holder, key) {
-
-// The walk method is used to recursively walk the resulting structure so
-// that modifications can be made.
-
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
-
-
-// Parsing happens in four stages. In the first stage, we replace certain
-// Unicode characters with escape sequences. JavaScript handles many characters
-// incorrectly, either silently deleting them, or treating them as line endings.
-
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' +
- ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- });
- }
-
-// In the second stage, we run the text against regular expressions that look
-// for non-JSON patterns. We are especially concerned with '()' and 'new'
-// because they can cause invocation, and '=' because it can cause mutation.
-// But just to be safe, we want to reject all unexpected forms.
-
-// We split the second stage into 4 regexp operations in order to work around
-// crippling inefficiencies in IE's and Safari's regexp engines. First we
-// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
-// replace all simple value tokens with ']' characters. Third, we delete all
-// open brackets that follow a colon or comma or that begin the text. Finally,
-// we look to see that the remaining characters are only whitespace or ']' or
-// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
- if (/^[\],:{}\s]*$/.
-test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
-replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
-replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
-
-// In the third stage we use the eval function to compile the text into a
-// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
-// in JavaScript: it can begin a block or an object literal. We wrap the text
-// in parens to eliminate the ambiguity.
-
- j = eval('(' + text + ')');
-
-// In the optional fourth stage, we recursively walk the new structure, passing
-// each name/value pair to a reviver function for possible transformation.
-
- return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
- }
-
-// If the text is not JSON parseable, then a SyntaxError is thrown.
-
- throw new SyntaxError('JSON.parse');
- };
- }
-}());
diff --git a/plugins/Recaptcha/RecaptchaPlugin.php b/plugins/Recaptcha/RecaptchaPlugin.php
index db118dbb8..3665214f8 100644
--- a/plugins/Recaptcha/RecaptchaPlugin.php
+++ b/plugins/Recaptcha/RecaptchaPlugin.php
@@ -62,9 +62,8 @@ class RecaptchaPlugin extends Plugin
function onEndRegistrationFormData($action)
{
- $action->style('#recaptcha_area{float:left;}');
$action->elementStart('li');
- $action->raw('<label for="recaptcha_area">Captcha</label>');
+ $action->raw('<label for="recaptcha">Captcha</label>');
if($this->checkssl() === true) {
$action->raw(recaptcha_get_html($this->public_key), null, true);
} else {
diff --git a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php
index 04adbf00e..3581f1de9 100644
--- a/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php
+++ b/plugins/RequireValidatedEmail/RequireValidatedEmailPlugin.php
@@ -96,5 +96,16 @@ class RequireValidatedEmailPlugin extends Plugin
}
return false;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Require Validated Email',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews, Evan Prodromou, Brion Vibber',
+ 'homepage' => 'http://status.net/wiki/Plugin:RequireValidatedEmail',
+ 'rawdescription' =>
+ _m('The Require Validated Email plugin disables posting for accounts that do not have a validated email address.'));
+ return true;
+ }
}
diff --git a/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php b/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php
index d48283b2e..d9d2137f8 100644
--- a/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php
+++ b/plugins/ReverseUsernameAuthentication/ReverseUsernameAuthenticationPlugin.php
@@ -31,8 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
-require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php';
-
class ReverseUsernameAuthenticationPlugin extends AuthenticationPlugin
{
//---interface implementation---//
@@ -55,4 +53,15 @@ class ReverseUsernameAuthenticationPlugin extends AuthenticationPlugin
$registration_data['nickname'] = $username ;
return User::register($registration_data);
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Reverse Username Authentication',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:ReverseUsernameAuthentication',
+ 'rawdescription' =>
+ _m('The Reverse Username Authentication plugin allows for StatusNet to handle authentication by checking if the provided password is the same as the reverse of the username.'));
+ return true;
+ }
}
diff --git a/plugins/Sample/SamplePlugin.php b/plugins/Sample/SamplePlugin.php
index 7ea956af6..913741226 100644
--- a/plugins/Sample/SamplePlugin.php
+++ b/plugins/Sample/SamplePlugin.php
@@ -266,5 +266,16 @@ class SamplePlugin extends Plugin
_m('Hello'), _m('A warm greeting'), false, 'nav_hello');
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Sample',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Brion Vibber, Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:Sample',
+ 'rawdescription' =>
+ _m('A sample plugin to show basics of development for new hackers.'));
+ return true;
+ }
}
diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php
index 45b745b07..6eac7dbb1 100644
--- a/plugins/SimpleUrl/SimpleUrlPlugin.php
+++ b/plugins/SimpleUrl/SimpleUrlPlugin.php
@@ -47,5 +47,18 @@ class SimpleUrlPlugin extends UrlShortenerPlugin
protected function shorten($url) {
return $this->http_get(sprintf($this->serviceUrl,urlencode($url)));
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => sprintf('SimpleUrl (%s)', $this->shortenerName),
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:SimpleUrl',
+ 'rawdescription' =>
+ sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
+ $this->shortenerName));
+
+ return true;
+ }
}
diff --git a/plugins/TemplatePlugin.php b/plugins/TemplatePlugin.php
index 18aa8034c..80625c5b7 100644
--- a/plugins/TemplatePlugin.php
+++ b/plugins/TemplatePlugin.php
@@ -22,13 +22,13 @@ if (!defined('STATUSNET')) {
define('TEMPLATEPLUGIN_VERSION', '0.1');
class TemplatePlugin extends Plugin {
-
+
var $blocks = array();
-
+
function __construct() {
parent::__construct();
}
-
+
// capture the RouterInitialized event
// and connect a new API method
// for updating the template
@@ -37,8 +37,7 @@ class TemplatePlugin extends Plugin {
'action' => 'template',
));
}
-
-
+
// <%styles%>
// <%scripts%>
// <%search%>
@@ -50,18 +49,18 @@ class TemplatePlugin extends Plugin {
$act->extraHead();
$this->blocks['head'] = $act->xw->flush();
$act->showStylesheets();
- $this->blocks['styles'] = $act->xw->flush();
+ $this->blocks['styles'] = $act->xw->flush();
$act->showScripts();
- $this->blocks['scripts'] = $act->xw->flush();
+ $this->blocks['scripts'] = $act->xw->flush();
$act->showFeeds();
- $this->blocks['feeds'] = $act->xw->flush();
+ $this->blocks['feeds'] = $act->xw->flush();
$act->showOpenSearch();
- $this->blocks['search'] = $act->xw->flush();
+ $this->blocks['search'] = $act->xw->flush();
$act->showDescription();
$this->blocks['description'] = $act->xw->flush();
return false;
}
-
+
// <%bodytext%>
function onStartShowContentBlock( &$act ) {
$this->clear_xmlWriter($act);
@@ -70,7 +69,7 @@ class TemplatePlugin extends Plugin {
function onEndShowContentBlock( &$act ) {
$this->blocks['bodytext'] = $act->xw->flush();
}
-
+
// <%localnav%>
function onStartShowLocalNavBlock( &$act ) {
$this->clear_xmlWriter($act);
@@ -79,7 +78,7 @@ class TemplatePlugin extends Plugin {
function onEndShowLocalNavBlock( &$act ) {
$this->blocks['localnav'] = $act->xw->flush();
}
-
+
// <%export%>
function onStartShowExportData( &$act ) {
$this->clear_xmlWriter($act);
@@ -88,7 +87,7 @@ class TemplatePlugin extends Plugin {
function onEndShowExportData( &$act ) {
$this->blocks['export'] = $act->xw->flush();
}
-
+
// <%subscriptions%>
// <%subscribers%>
// <%groups%>
@@ -149,7 +148,7 @@ class TemplatePlugin extends Plugin {
}
return false;
}
-
+
// <%logo%>
// <%nav%>
// <%notice%>
@@ -170,7 +169,7 @@ class TemplatePlugin extends Plugin {
$this->blocks['noticeform'] = $act->xw->flush();
return false;
}
-
+
// <%secondarynav%>
// <%licenses%>
function onStartShowFooter( &$act ) {
@@ -181,19 +180,19 @@ class TemplatePlugin extends Plugin {
$this->blocks['licenses'] = $act->xw->flush();
return false;
}
-
+
// capture the EndHTML event
// and include the template
function onEndEndHTML($act) {
-
+
global $action, $tags;
-
+
// set the action and title values
$vars = array(
'action'=>$action,
'title'=>$act->title(). " - ". common_config('site', 'name')
);
-
+
// use the PHP template
// unless statusnet config:
// $config['template']['mode'] = 'html';
@@ -203,55 +202,55 @@ class TemplatePlugin extends Plugin {
include $tpl_file;
return;
}
-
+
$tpl_file = $this->templateFolder() . '/index.html';
-
+
// read the static template
$output = file_get_contents( $tpl_file );
-
+
$tags = array();
-
+
// get a list of the <%tags%> in the template
$pattern='/<%([a-z]+)%>/';
-
+
if ( 1 <= preg_match_all( $pattern, $output, $found ))
$tags[] = $found;
-
+
// for each found tag, set its value from the rendered blocks
foreach( $tags[0][1] as $pos=>$tag ) {
if (isset($this->blocks[$tag]))
$vars[$tag] = $this->blocks[$tag];
-
+
// didn't find a block for the tag
elseif (!isset($vars[$tag]))
$vars[$tag] = '';
}
-
+
// replace the tags in the template
foreach( $vars as $key=>$val )
$output = str_replace( '<%'.$key.'%>', $val, $output );
-
+
echo $output;
-
+
return true;
-
+
}
function templateFolder() {
return 'tpl';
}
-
+
// catching the StartShowHTML event to halt the rendering
function onStartShowHTML( &$act ) {
$this->clear_xmlWriter($act);
return true;
}
-
+
// clear the xmlWriter
function clear_xmlWriter( &$act ) {
$act->xw->openMemory();
$act->xw->setIndent(true);
}
-
+
}
/**
@@ -267,7 +266,7 @@ class TemplatePlugin extends Plugin {
* @link http://megapump.com/
*
*/
-
+
class TemplateAction extends Action
{
@@ -275,54 +274,65 @@ class TemplateAction extends Action
parent::prepare($args);
return true;
}
-
+
function handle($args) {
-
+
parent::handle($args);
-
+
if (!isset($_SERVER['PHP_AUTH_USER'])) {
-
+
// not authenticated, show login form
header('WWW-Authenticate: Basic realm="StatusNet API"');
-
+
// cancelled the browser login form
$this->clientError(_('Authentication error!'), $code = 401);
-
+
} else {
-
+
$nick = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
-
+
// check username and password
$user = common_check_user($nick,$pass);
-
+
if ($user) {
-
+
// verify that user is admin
if (!($user->id == 1))
$this->clientError(_('Only User #1 can update the template.'), $code = 401);
-
+
// open the old template
$tpl_file = $this->templateFolder() . '/index.html';
$fp = fopen( $tpl_file, 'w+' );
-
+
// overwrite with the new template
fwrite($fp, $this->arg('template'));
fclose($fp);
-
+
header('HTTP/1.1 200 OK');
header('Content-type: text/plain');
print "Template Updated!";
-
+
} else {
-
+
// bad username and password
$this->clientError(_('Authentication error!'), $code = 401);
-
+
}
-
+
}
}
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'Template',
+ 'version' => TEMPLATEPLUGIN_VERSION,
+ 'author' => 'Brian Hendrickson',
+ 'homepage' => 'http://status.net/wiki/Plugin:Template',
+ 'rawdescription' =>
+ _m('Use an HTML template for Web output.'));
+ return true;
+ }
+
}
/**
diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php
index 6ced9afdc..e2d494a7b 100644
--- a/plugins/TightUrl/TightUrlPlugin.php
+++ b/plugins/TightUrl/TightUrlPlugin.php
@@ -57,4 +57,16 @@ class TightUrlPlugin extends UrlShortenerPlugin
return strval($xml['href']);
}
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => sprintf('TightUrl (%s)', $this->shortenerName),
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:TightUrl',
+ 'rawdescription' =>
+ sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
+ $this->shortenerName));
+ return true;
+ }
}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index de1181903..a87ee2894 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -31,6 +31,8 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
+
/**
* Plugin for sending and importing Twitter statuses
*
@@ -189,4 +191,17 @@ class TwitterBridgePlugin extends Plugin
return true;
}
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'TwitterBridge',
+ 'version' => TWITTERBRIDGEPLUGIN_VERSION,
+ 'author' => 'Zach Copley',
+ 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
+ 'rawdescription' =>
+ _m('The Twitter "bridge" plugin allows you to integrate ' .
+ 'your StatusNet instance with ' .
+ '<a href="http://twitter.com/">Twitter</a>.'));
+ return true;
+ }
+
}
diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php
index 602a5bfa8..a33869c19 100644
--- a/plugins/UserFlag/UserFlagPlugin.php
+++ b/plugins/UserFlag/UserFlagPlugin.php
@@ -102,20 +102,20 @@ class UserFlagPlugin extends Plugin
function onAutoload($cls)
{
- switch ($cls)
+ switch (strtolower($cls))
{
- case 'FlagprofileAction':
- case 'AdminprofileflagAction':
- case 'ClearflagAction':
+ case 'flagprofileaction':
+ case 'adminprofileflagaction':
+ case 'clearflagaction':
include_once INSTALLDIR.'/plugins/UserFlag/' .
strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
- case 'FlagProfileForm':
- case 'ClearFlagForm':
+ case 'flagprofileform':
+ case 'clearflagform':
include_once INSTALLDIR.'/plugins/UserFlag/' . strtolower($cls . '.php');
return false;
- case 'User_flag_profile':
- include_once INSTALLDIR.'/plugins/UserFlag/'.$cls.'.php';
+ case 'user_flag_profile':
+ include_once INSTALLDIR.'/plugins/UserFlag/'.ucfirst(strtolower($cls)).'.php';
return false;
default:
return true;
@@ -258,4 +258,39 @@ class UserFlagPlugin extends Plugin
}
return true;
}
+
+ /**
+ * Ensure that flag entries for a profile are deleted
+ * along with the profile when deleting users.
+ * This prevents breakage of the admin profile flag UI.
+ *
+ * @param Profile $profile
+ * @param array &$related list of related tables; entries
+ * with matching profile_id will be deleted.
+ *
+ * @return boolean hook result
+ */
+
+ function onProfileDeleteRelated($profile, &$related)
+ {
+ $related[] = 'user_flag_profile';
+ return true;
+ }
+
+ /**
+ * Ensure that flag entries created by a user are deleted
+ * when that user gets deleted.
+ *
+ * @param User $user
+ * @param array &$related list of related tables; entries
+ * with matching user_id will be deleted.
+ *
+ * @return boolean hook result
+ */
+
+ function onUserDeleteRelated($user, &$related)
+ {
+ $related[] = 'user_flag_profile';
+ return true;
+ }
}
diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php
index 658259452..bc4251cf7 100644
--- a/plugins/UserFlag/User_flag_profile.php
+++ b/plugins/UserFlag/User_flag_profile.php
@@ -90,6 +90,17 @@ class User_flag_profile extends Memcached_DataObject
}
/**
+ * return key definitions for DB_DataObject
+ *
+ * @return array key definitions
+ */
+
+ function keyTypes()
+ {
+ return $this->keys();
+ }
+
+ /**
* Get a single object with multiple keys
*
* @param array $kv Map of key-value pairs
@@ -97,7 +108,7 @@ class User_flag_profile extends Memcached_DataObject
* @return User_flag_profile found object or null
*/
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_flag_profile', $kv);
}
diff --git a/plugins/WikiHashtagsPlugin.php b/plugins/WikiHashtagsPlugin.php
index 334fc13ba..c6c976b8f 100644
--- a/plugins/WikiHashtagsPlugin.php
+++ b/plugins/WikiHashtagsPlugin.php
@@ -31,8 +31,6 @@ if (!defined('STATUSNET')) {
exit(1);
}
-define('WIKIHASHTAGSPLUGIN_VERSION', '0.1');
-
/**
* Plugin to use WikiHashtags
*
@@ -47,6 +45,8 @@ define('WIKIHASHTAGSPLUGIN_VERSION', '0.1');
class WikiHashtagsPlugin extends Plugin
{
+ const VERSION = '0.1';
+
function __construct($code=null)
{
parent::__construct();
@@ -99,4 +99,15 @@ class WikiHashtagsPlugin extends Plugin
return true;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'WikiHashtags',
+ 'version' => self::VERSION,
+ 'author' => 'Evan Prodromou',
+ 'homepage' => 'http://status.net/wiki/Plugin:WikiHashtags',
+ 'rawdescription' =>
+ _m('Gets hashtag descriptions from <a href="http://hashtags.wikia.com/">WikiHashtags</a>.'));
+ return true;
+ }
}
diff --git a/plugins/XCachePlugin.php b/plugins/XCachePlugin.php
index 03cb0c06e..2baa290ed 100644
--- a/plugins/XCachePlugin.php
+++ b/plugins/XCachePlugin.php
@@ -109,5 +109,16 @@ class XCachePlugin extends Plugin
Event::handle('EndCacheDelete', array($key));
return false;
}
+
+ function onPluginVersion(&$versions)
+ {
+ $versions[] = array('name' => 'XCache',
+ 'version' => STATUSNET_VERSION,
+ 'author' => 'Craig Andrews',
+ 'homepage' => 'http://status.net/wiki/Plugin:XCache',
+ 'rawdescription' =>
+ _m('Use the <a href="http://xcache.lighttpd.net/">XCache</a> variable cache to cache query results.'));
+ return true;
+ }
}