summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/action.php4
-rw-r--r--lib/adminpanelaction.php46
-rw-r--r--lib/authenticationplugin.php231
-rw-r--r--lib/authorizationplugin.php105
-rw-r--r--lib/cache.php182
-rw-r--r--lib/columndef.php1
-rw-r--r--lib/common.php33
-rw-r--r--lib/default.php27
-rw-r--r--lib/grouptagcloudsection.php7
-rw-r--r--lib/htmloutputter.php2
-rw-r--r--lib/jabber.php2
-rw-r--r--lib/jsonsearchresultslist.php10
-rw-r--r--lib/mailhandler.php275
-rw-r--r--lib/noticeform.php22
-rw-r--r--lib/noticelist.php10
-rw-r--r--lib/personaltagcloudsection.php11
-rw-r--r--lib/ping.php4
-rw-r--r--lib/plugin.php11
-rw-r--r--lib/popularnoticesection.php6
-rw-r--r--lib/router.php5
-rw-r--r--lib/schema.php8
-rw-r--r--lib/util.php52
22 files changed, 980 insertions, 74 deletions
diff --git a/lib/action.php b/lib/action.php
index dac0e2583..1b4cb5cec 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -252,6 +252,8 @@ class Action extends HTMLOutputter // lawsuit
if (Event::handle('StartShowJQueryScripts', array($this))) {
$this->script('js/jquery.min.js');
$this->script('js/jquery.form.js');
+ $this->script('js/jquery.cookie.js');
+ $this->script('js/json2.js');
$this->script('js/jquery.joverlay.min.js');
Event::handle('EndShowJQueryScripts', array($this));
}
@@ -735,6 +737,8 @@ class Action extends HTMLOutputter // lawsuit
_('Privacy'));
$this->menuItem(common_local_url('doc', array('title' => 'source')),
_('Source'));
+ $this->menuItem(common_local_url('version'),
+ _('Version'));
$this->menuItem(common_local_url('doc', array('title' => 'contact')),
_('Contact'));
$this->menuItem(common_local_url('doc', array('title' => 'badge')),
diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php
index 7997eb2b1..a6981ac61 100644
--- a/lib/adminpanelaction.php
+++ b/lib/adminpanelaction.php
@@ -70,7 +70,7 @@ class AdminPanelAction extends Action
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
- return;
+ return false;
}
$user = common_current_user();
@@ -94,7 +94,18 @@ class AdminPanelAction extends Action
if (!$user->hasRight(Right::CONFIGURESITE)) {
$this->clientError(_('You cannot make changes to this site.'));
- return;
+ return false;
+ }
+
+ // This panel must be enabled
+
+ $name = $this->trimmed('action');
+
+ $name = mb_substr($name, 0, -10);
+
+ if (!in_array($name, common_config('admin', 'panels'))) {
+ $this->clientError(_('Changes to that panel are not allowed.'), 403);
+ return false;
}
return true;
@@ -224,7 +235,7 @@ class AdminPanelAction extends Action
$this->clientError(_('saveSettings() not implemented.'));
return;
}
-
+
/**
* Delete a design setting
*
@@ -296,20 +307,33 @@ class AdminPanelNav extends Widget
if (Event::handle('StartAdminPanelNav', array($this))) {
- $this->out->menuItem(common_local_url('siteadminpanel'), _('Site'),
- _('Basic site configuration'), $action_name == 'siteadminpanel', 'nav_site_admin_panel');
+ if ($this->canAdmin('site')) {
+ $this->out->menuItem(common_local_url('siteadminpanel'), _('Site'),
+ _('Basic site configuration'), $action_name == 'siteadminpanel', 'nav_site_admin_panel');
+ }
- $this->out->menuItem(common_local_url('designadminpanel'), _('Design'),
- _('Design configuration'), $action_name == 'designadminpanel', 'nav_design_admin_panel');
+ if ($this->canAdmin('design')) {
+ $this->out->menuItem(common_local_url('designadminpanel'), _('Design'),
+ _('Design configuration'), $action_name == 'designadminpanel', 'nav_design_admin_panel');
+ }
- $this->out->menuItem(common_local_url('useradminpanel'), _('User'),
- _('Paths configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel');
+ if ($this->canAdmin('user')) {
+ $this->out->menuItem(common_local_url('useradminpanel'), _('User'),
+ _('Paths configuration'), $action_name == 'useradminpanel', 'nav_design_admin_panel');
+ }
- $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
- _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel');
+ if ($this->canAdmin('paths')) {
+ $this->out->menuItem(common_local_url('pathsadminpanel'), _('Paths'),
+ _('Paths configuration'), $action_name == 'pathsadminpanel', 'nav_design_admin_panel');
+ }
Event::handle('EndAdminPanelNav', array($this));
}
$this->action->elementEnd('ul');
}
+
+ function canAdmin($name)
+ {
+ return in_array($name, common_config('admin', 'panels'));
+ }
}
diff --git a/lib/authenticationplugin.php b/lib/authenticationplugin.php
new file mode 100644
index 000000000..de479a576
--- /dev/null
+++ b/lib/authenticationplugin.php
@@ -0,0 +1,231 @@
+<?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))){
+ //unlike most Event::handle lines of code, this one has a ! (not)
+ //we want to do this if the event *was* handled - this isn't a "default" implementation
+ //like most code of this form.
+ 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 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/lib/authorizationplugin.php b/lib/authorizationplugin.php
new file mode 100644
index 000000000..733b0c065
--- /dev/null
+++ b/lib/authorizationplugin.php
@@ -0,0 +1,105 @@
+<?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 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/lib/cache.php b/lib/cache.php
new file mode 100644
index 000000000..b7b34c050
--- /dev/null
+++ b/lib/cache.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Cache interface plus default in-memory cache implementation
+ *
+ * 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 Cache
+ * @package StatusNet
+ * @author Evan Prodromou <evan@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/
+ */
+
+/**
+ * Interface for caching
+ *
+ * An abstract interface for caching. Because we originally used the
+ * Memcache plugin directly, the interface uses a small subset of the
+ * Memcache interface.
+ *
+ * @category Cache
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class Cache
+{
+ var $_items = array();
+ static $_inst = null;
+
+ /**
+ * Singleton constructor
+ *
+ * Use this to get the singleton instance of Cache.
+ *
+ * @return Cache cache object
+ */
+
+ static function instance()
+ {
+ if (is_null(self::$_inst)) {
+ self::$_inst = new Cache();
+ }
+
+ return self::$_inst;
+ }
+
+ /**
+ * Create a cache key from input text
+ *
+ * Builds a cache key from input text. Helps to namespace
+ * the cache area (if shared with other applications or sites)
+ * and prevent conflicts.
+ *
+ * @param string $extra the real part of the key
+ *
+ * @return string full key
+ */
+
+ static function key($extra)
+ {
+ $base_key = common_config('cache', 'base');
+
+ if (empty($base_key)) {
+ $base_key = common_keyize(common_config('site', 'name'));
+ }
+
+ return 'statusnet:' . $base_key . ':' . $extra;
+ }
+
+ /**
+ * Make a string suitable for use as a key
+ *
+ * Useful for turning primary keys of tables into cache keys.
+ *
+ * @param string $str string to turn into a key
+ *
+ * @return string keyized string
+ */
+
+ static function keyize($str)
+ {
+ $str = strtolower($str);
+ $str = preg_replace('/\s/', '_', $str);
+ return $str;
+ }
+
+ /**
+ * Get a value associated with a key
+ *
+ * The value should have been set previously.
+ *
+ * @param string $key Lookup key
+ *
+ * @return string retrieved value or null if unfound
+ */
+
+ function get($key)
+ {
+ $value = false;
+
+ if (Event::handle('StartCacheGet', array(&$key, &$value))) {
+ if (array_key_exists($key, $this->_items)) {
+ $value = unserialize($this->_items[$key]);
+ }
+ Event::handle('EndCacheGet', array($key, &$value));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Set the value associated with a key
+ *
+ * @param string $key The key to use for lookups
+ * @param string $value The value to store
+ * @param integer $flag Flags to use, mostly ignored
+ * @param integer $expiry Expiry value, mostly ignored
+ *
+ * @return boolean success flag
+ */
+
+ function set($key, $value, $flag=null, $expiry=null)
+ {
+ $success = false;
+
+ if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag,
+ &$expiry, &$success))) {
+
+ $this->_items[$key] = serialize($value);
+
+ $success = true;
+
+ Event::handle('EndCacheSet', array($key, $value, $flag,
+ $expiry));
+ }
+
+ return $success;
+ }
+
+ /**
+ * Delete the value associated with a key
+ *
+ * @param string $key Key to delete
+ *
+ * @return boolean success flag
+ */
+
+ function delete($key)
+ {
+ $success = false;
+
+ if (Event::handle('StartCacheDelete', array(&$key, &$success))) {
+ if (array_key_exists($key, $this->_items)) {
+ unset($this->_items[$key]);
+ }
+ $success = true;
+ Event::handle('EndCacheDelete', array($key));
+ }
+
+ return $success;
+ }
+}
diff --git a/lib/columndef.php b/lib/columndef.php
index 1bae6b33b..ac2fcd23e 100644
--- a/lib/columndef.php
+++ b/lib/columndef.php
@@ -74,6 +74,7 @@ class ColumnDef
* @param string $key type of key
* @param value $default default value
* @param value $extra unused
+ * @param boolean $auto_increment
*/
function __construct($name=null, $type=null, $size=null,
diff --git a/lib/common.php b/lib/common.php
index 7fa1910af..fb5e5919e 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -210,6 +210,18 @@ if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'
$config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini';
}
+// Backwards compatibility
+
+if (array_key_exists('memcached', $config)) {
+ if ($config['memcached']['enabled']) {
+ addPlugin('Memcache', array('servers' => $config['memcached']['server']));
+ }
+
+ if (!empty($config['memcached']['base'])) {
+ $config['cache']['base'] = $config['memcached']['base'];
+ }
+}
+
function __autoload($cls)
{
if (file_exists(INSTALLDIR.'/classes/' . $cls . '.php')) {
@@ -226,6 +238,27 @@ function __autoload($cls)
}
}
+// Load default plugins
+
+foreach ($config['plugins']['default'] as $name => $params) {
+ if (is_null($params)) {
+ addPlugin($name);
+ } else if (is_array($params)) {
+ if (count($params) == 0) {
+ addPlugin($name);
+ } else {
+ $keys = array_keys($params);
+ if (is_string($keys[0])) {
+ addPlugin($name, $params);
+ } else {
+ foreach ($params as $paramset) {
+ addPlugin($name, $paramset);
+ }
+ }
+ }
+ }
+}
+
// XXX: how many of these could be auto-loaded on use?
// XXX: note that these files should not use config options
// at compile time since DB config options are not yet loaded.
diff --git a/lib/default.php b/lib/default.php
index 8a70ed3fa..fa862f3ff 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -54,6 +54,7 @@ $default =
'dupelimit' => 60, # default for same person saying the same thing
'textlimit' => 140,
'indent' => true,
+ 'use_x_sendfile' => false,
),
'db' =>
array('database' => 'YOU HAVE TO SET THIS IN config.php',
@@ -147,11 +148,8 @@ $default =
array('enabled' => true,
'consumer_key' => null,
'consumer_secret' => null),
- 'memcached' =>
- array('enabled' => false,
- 'server' => 'localhost',
- 'base' => null,
- 'port' => 11211),
+ 'cache' =>
+ array('base' => null),
'ping' =>
array('notify' => array()),
'inboxes' =>
@@ -232,4 +230,23 @@ $default =
array('timeout' => 5), // HTTP request timeout in seconds when contacting remote hosts for OMB updates
'logincommand' =>
array('disabled' => true),
+ 'plugins' =>
+ array('default' => array('LilUrl' => array('shortenerName'=>'ur1.ca',
+ 'freeService' => true,
+ 'serviceUrl'=>'http://ur1.ca/'),
+ 'PtitUrl' => array('shortenerName' => 'ptiturl.com',
+ 'serviceUrl' => 'http://ptiturl.com/?creer=oui&action=Reduire&url=%1$s'),
+ 'SimpleUrl' => array(array('shortenerName' => 'is.gd', 'serviceUrl' => 'http://is.gd/api.php?longurl=%1$s'),
+ array('shortenerName' => 'snipr.com', 'serviceUrl' => 'http://snipr.com/site/snip?r=simple&link=%1$s'),
+ array('shortenerName' => 'metamark.net', 'serviceUrl' => 'http://metamark.net/api/rest/simple?long_url=%1$s'),
+ array('shortenerName' => 'tinyurl.com', 'serviceUrl' => 'http://tinyurl.com/api-create.php?url=%1$s')),
+ 'TightUrl' => array('shortenerName' => '2tu.us', 'freeService' => true,'serviceUrl'=>'http://2tu.us/?save=y&url=%1$s'),
+ 'Geonames' => null,
+ 'Mapstraction' => null,
+ 'Linkback' => null,
+ 'WikiHashtags' => null,
+ 'OpenID' => null),
+ ),
+ 'admin' =>
+ array('panels' => array('design', 'site', 'user', 'paths')),
);
diff --git a/lib/grouptagcloudsection.php b/lib/grouptagcloudsection.php
index 091cf4845..14ceda085 100644
--- a/lib/grouptagcloudsection.php
+++ b/lib/grouptagcloudsection.php
@@ -58,11 +58,7 @@ class GroupTagCloudSection extends TagCloudSection
function getTags()
{
- if (common_config('db', 'type') == 'pgsql') {
- $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))';
- } else {
- $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))';
- }
+ $weightexpr = common_sql_weight('notice_tag.created', common_config('tag', 'dropoff'));
$names = $this->group->getAliases();
@@ -99,7 +95,6 @@ class GroupTagCloudSection extends TagCloudSection
$tag = Memcached_DataObject::cachedQuery('Notice_tag',
sprintf($qry,
- common_config('tag', 'dropoff'),
$this->group->id,
$namestring),
3600);
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 2091c6e2c..31660ce95 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -352,7 +352,7 @@ class HTMLOutputter extends XMLOutputter
{
if(Event::handle('StartScriptElement', array($this,&$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 = common_path($src) . '?version=' . STATUSNET_VERSION;
}
diff --git a/lib/jabber.php b/lib/jabber.php
index 01aed8ffa..a821856a8 100644
--- a/lib/jabber.php
+++ b/lib/jabber.php
@@ -440,7 +440,7 @@ function jabber_public_notice($notice)
// XXX: should we send out non-local messages if public,localonly
// = false? I think not
- if ($public && $notice->is_local) {
+ if ($public && $notice->is_local == Notice::LOCAL_PUBLIC) {
$profile = Profile::staticGet($notice->profile_id);
if (!$profile) {
diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php
index 569bfa873..0d72ddf7a 100644
--- a/lib/jsonsearchresultslist.php
+++ b/lib/jsonsearchresultslist.php
@@ -105,8 +105,14 @@ class JSONSearchResultsList
break;
}
- $item = new ResultItem($this->notice);
- array_push($this->results, $item);
+ $profile = $this->notice->getProfile();
+
+ // Don't show notices from deleted users
+
+ if (!empty($profile)) {
+ $item = new ResultItem($this->notice);
+ array_push($this->results, $item);
+ }
}
$time_end = microtime(true);
diff --git a/lib/mailhandler.php b/lib/mailhandler.php
new file mode 100644
index 000000000..32a8cd9bc
--- /dev/null
+++ b/lib/mailhandler.php
@@ -0,0 +1,275 @@
+<?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/>.
+ */
+
+require_once(INSTALLDIR . '/lib/mail.php');
+require_once(INSTALLDIR . '/lib/mediafile.php');
+require_once('Mail/mimeDecode.php');
+
+# FIXME: we use both Mail_mimeDecode and mailparse
+# Need to move everything to mailparse
+
+class MailHandler
+{
+ function __construct()
+ {
+ }
+
+ function handle_message($rawmessage)
+ {
+ list($from, $to, $msg, $attachments) = $this->parse_message($rawmessage);
+ if (!$from || !$to || !$msg) {
+ $this->error(null, _('Could not parse message.'));
+ }
+ common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20));
+ $user = $this->user_from_header($from);
+ if (!$user) {
+ $this->error($from, _('Not a registered user.'));
+ return false;
+ }
+ if (!$this->user_match_to($user, $to)) {
+ $this->error($from, _('Sorry, that is not your incoming email address.'));
+ return false;
+ }
+ if (!$user->emailpost) {
+ $this->error($from, _('Sorry, no incoming email allowed.'));
+ return false;
+ }
+ $response = $this->handle_command($user, $from, $msg);
+ if ($response) {
+ return true;
+ }
+ $msg = $this->cleanup_msg($msg);
+ $msg = common_shorten_links($msg);
+ if (Notice::contentTooLong($msg)) {
+ $this->error($from, sprintf(_('That\'s too long. '.
+ 'Max notice size is %d chars.'),
+ Notice::maxContent()));
+ }
+
+ $mediafiles = array();
+
+ foreach($attachments as $attachment){
+
+ $mf = null;
+
+ try {
+ $mf = MediaFile::fromFileHandle($attachment, $user);
+ } catch(ClientException $ce) {
+ $this->error($from, $ce->getMessage());
+ }
+
+ $msg .= ' ' . $mf->shortUrl();
+
+ array_push($mediafiles, $mf);
+ fclose($attachment);
+ }
+
+ $err = $this->add_notice($user, $msg, $mediafiles);
+
+ if (is_string($err)) {
+ $this->error($from, $err);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function error($from, $msg)
+ {
+ file_put_contents("php://stderr", $msg . "\n");
+ exit(1);
+ }
+
+ function user_from_header($from_hdr)
+ {
+ $froms = mailparse_rfc822_parse_addresses($from_hdr);
+ if (!$froms) {
+ return null;
+ }
+ $from = $froms[0];
+ $addr = common_canonical_email($from['address']);
+ $user = User::staticGet('email', $addr);
+ if (!$user) {
+ $user = User::staticGet('smsemail', $addr);
+ }
+ return $user;
+ }
+
+ function user_match_to($user, $to_hdr)
+ {
+ $incoming = $user->incomingemail;
+ $tos = mailparse_rfc822_parse_addresses($to_hdr);
+ foreach ($tos as $to) {
+ if (strcasecmp($incoming, $to['address']) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function handle_command($user, $from, $msg)
+ {
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $msg);
+ if ($cmd) {
+ $cmd->execute(new MailChannel($from));
+ return true;
+ }
+ return false;
+ }
+
+ function respond($from, $to, $response)
+ {
+
+ $headers['From'] = $to;
+ $headers['To'] = $from;
+ $headers['Subject'] = "Command complete";
+
+ return mail_send(array($from), $headers, $response);
+ }
+
+ function log($level, $msg)
+ {
+ common_log($level, 'MailDaemon: '.$msg);
+ }
+
+ function add_notice($user, $msg, $mediafiles)
+ {
+ try {
+ $notice = Notice::saveNew($user->id, $msg, 'mail');
+ } catch (Exception $e) {
+ $this->log(LOG_ERR, $e->getMessage());
+ return $e->getMessage();
+ }
+ foreach($mediafiles as $mf){
+ $mf->attachToNotice($notice);
+ }
+ common_broadcast_notice($notice);
+ $this->log(LOG_INFO,
+ 'Added notice ' . $notice->id . ' from user ' . $user->nickname);
+ return true;
+ }
+
+ function parse_message($contents)
+ {
+ $parsed = Mail_mimeDecode::decode(array('input' => $contents,
+ 'include_bodies' => true,
+ 'decode_headers' => true,
+ 'decode_bodies' => true));
+ if (!$parsed) {
+ return null;
+ }
+
+ $from = $parsed->headers['from'];
+
+ $to = $parsed->headers['to'];
+
+ $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
+
+ $attachments = array();
+
+ $this->extract_part($parsed,$msg,$attachments);
+
+ return array($from, $to, $msg, $attachments);
+ }
+
+ function extract_part($parsed,&$msg,&$attachments){
+ if ($parsed->ctype_primary == 'multipart') {
+ if($parsed->ctype_secondary == 'alternative'){
+ $altmsg = $this->extract_msg_from_multipart_alternative_part($parsed);
+ if(!empty($altmsg)) $msg = $altmsg;
+ }else{
+ foreach($parsed->parts as $part){
+ $this->extract_part($part,$msg,$attachments);
+ }
+ }
+ } else if ($parsed->ctype_primary == 'text'
+ && $parsed->ctype_secondary=='plain') {
+ $msg = $parsed->body;
+ if(strtolower($parsed->ctype_parameters['charset']) != "utf-8"){
+ $msg = utf8_encode($msg);
+ }
+ }else if(!empty($parsed->body)){
+ if(common_config('attachments', 'uploads')){
+ //only save attachments if uploads are enabled
+ $attachment = tmpfile();
+ fwrite($attachment, $parsed->body);
+ $attachments[] = $attachment;
+ }
+ }
+ }
+
+ function extract_msg_from_multipart_alternative_part($parsed){
+ foreach ($parsed->parts as $part) {
+ $this->extract_part($part,$msg,$attachments);
+ }
+ //we don't want any attachments that are a result of this parsing
+ return $msg;
+ }
+
+ function unsupported_type($type)
+ {
+ $this->error(null, "Unsupported message type: " . $type);
+ }
+
+ function cleanup_msg($msg)
+ {
+ $lines = explode("\n", $msg);
+
+ $output = '';
+
+ foreach ($lines as $line) {
+ // skip quotes
+ if (preg_match('/^\s*>.*$/', $line)) {
+ continue;
+ }
+ // skip start of quote
+ if (preg_match('/^\s*On.*wrote:\s*$/', $line)) {
+ continue;
+ }
+ // probably interesting to someone, not us
+ if (preg_match('/^\s*Sent via/', $line)) {
+ continue;
+ }
+ if (preg_match('/^\s*Sent from my/', $line)) {
+ continue;
+ }
+
+ // skip everything after a sig
+ if (preg_match('/^\s*--+\s*$/', $line) ||
+ preg_match('/^\s*__+\s*$/', $line))
+ {
+ break;
+ }
+ // skip everything after Outlook quote
+ if (preg_match('/^\s*-+\s*Original Message\s*-+\s*$/', $line)) {
+ break;
+ }
+ // skip everything after weird forward
+ if (preg_match('/^\s*Begin\s+forward/', $line)) {
+ break;
+ }
+
+ $output .= ' ' . $line;
+ }
+
+ preg_replace('/\s+/', ' ', $output);
+ return trim($output);
+ }
+}
diff --git a/lib/noticeform.php b/lib/noticeform.php
index 593a1e932..f0b704e87 100644
--- a/lib/noticeform.php
+++ b/lib/noticeform.php
@@ -110,6 +110,8 @@ class NoticeForm extends Form
$this->user = common_current_user();
}
+ $this->profile = $this->user->getProfile();
+
if (common_config('attachments', 'uploads')) {
$this->enctype = 'multipart/form-data';
}
@@ -198,12 +200,22 @@ class NoticeForm extends Form
$this->out->hidden('notice_return-to', $this->action, 'returnto');
}
$this->out->hidden('notice_in-reply-to', $this->inreplyto, 'inreplyto');
- $this->out->hidden('notice_data-lat', empty($this->lat) ? null : $this->lat, 'lat');
- $this->out->hidden('notice_data-lon', empty($this->lon) ? null : $this->lon, 'lon');
- $this->out->hidden('notice_data-location_id', empty($this->location_id) ? null : $this->location_id, 'location_id');
- $this->out->hidden('notice_data-location_ns', empty($this->location_ns) ? null : $this->location_ns, 'location_ns');
- Event::handle('StartShowNoticeFormData', array($this));
+ if ($this->user->shareLocation()) {
+ $this->out->hidden('notice_data-lat', empty($this->lat) ? (empty($this->profile->lat) ? null : $this->profile->lat) : $this->lat, 'lat');
+ $this->out->hidden('notice_data-lon', empty($this->lon) ? (empty($this->profile->lon) ? null : $this->profile->lon) : $this->lon, 'lon');
+ $this->out->hidden('notice_data-location_id', empty($this->location_id) ? (empty($this->profile->location_id) ? null : $this->profile->location_id) : $this->location_id, 'location_id');
+ $this->out->hidden('notice_data-location_ns', empty($this->location_ns) ? (empty($this->profile->location_ns) ? null : $this->profile->location_ns) : $this->location_ns, 'location_ns');
+
+ $this->out->elementStart('div', array('id' => 'notice_data-geo_wrap',
+ 'title' => common_local_url('geocode')));
+ $this->out->checkbox('notice_data-geo', _('Share my location'), true);
+ $this->out->elementEnd('div');
+ $this->out->inlineScript(' var NoticeDataGeoShareDisable_text = "'._('Do not share my location.').'";'.
+ ' var NoticeDataGeoInfoMinimize_text = "'._('Hide this info').'";');
+ }
+
+ Event::handle('EndShowNoticeFormData', array($this));
}
}
diff --git a/lib/noticelist.php b/lib/noticelist.php
index 4c11ceed6..78abf34a7 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -191,6 +191,14 @@ class NoticeListItem extends Widget
function show()
{
+ if (empty($this->notice)) {
+ common_log(LOG_WARNING, "Trying to show missing notice; skipping.");
+ return;
+ } else if (empty($this->profile)) {
+ common_log(LOG_WARNING, "Trying to show missing profile (" . $this->notice->profile_id . "); skipping.");
+ return;
+ }
+
$this->showStart();
if (Event::handle('StartShowNoticeItem', array($this))) {
$this->showNotice();
@@ -371,7 +379,7 @@ class NoticeListItem extends Widget
function showNoticeLink()
{
- if($this->notice->is_local){
+ if($this->notice->is_local == Notice::LOCAL_PUBLIC || $this->notice->is_local == Notice::LOCAL_NONPUBLIC){
$noticeurl = common_local_url('shownotice',
array('notice' => $this->notice->id));
}else{
diff --git a/lib/personaltagcloudsection.php b/lib/personaltagcloudsection.php
index 0b29d58ca..091425f92 100644
--- a/lib/personaltagcloudsection.php
+++ b/lib/personaltagcloudsection.php
@@ -58,13 +58,9 @@ class PersonalTagCloudSection extends TagCloudSection
function getTags()
{
- if (common_config('db', 'type') == 'pgsql') {
- $weightexpr='sum(exp(-extract(epoch from (now() - notice_tag.created)) / %s))';
- } else {
- $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))';
- }
-
- $qry = 'SELECT notice_tag.tag, '.
+ $weightexpr = common_sql_weight('notice_tag.created', common_config('tag', 'dropoff'));
+
+ $qry = 'SELECT notice_tag.tag, '.
$weightexpr . ' as weight ' .
'FROM notice_tag JOIN notice ' .
'ON notice_tag.notice_id = notice.id ' .
@@ -83,7 +79,6 @@ class PersonalTagCloudSection extends TagCloudSection
$tag = Memcached_DataObject::cachedQuery('Notice_tag',
sprintf($qry,
- common_config('tag', 'dropoff'),
$this->user->id),
3600);
return $tag;
diff --git a/lib/ping.php b/lib/ping.php
index 5698c4038..735af9ef1 100644
--- a/lib/ping.php
+++ b/lib/ping.php
@@ -21,7 +21,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
function ping_broadcast_notice($notice) {
- if (!$notice->is_local) {
+ if ($notice->is_local != Notice::LOCAL_PUBLIC && $notice->is_local != Notice::LOCAL_NONPUBLIC) {
return true;
}
@@ -115,4 +115,4 @@ function ping_notice_tags($notice) {
return implode('|', $tags);
}
return NULL;
-} \ No newline at end of file
+}
diff --git a/lib/plugin.php b/lib/plugin.php
index de7313e59..65ccdafbb 100644
--- a/lib/plugin.php
+++ b/lib/plugin.php
@@ -104,5 +104,16 @@ class Plugin
{
$this->log(LOG_DEBUG, $msg);
}
+
+ function onPluginVersion(&$versions)
+ {
+ $cls = get_class($this);
+ $name = mb_substr($cls, 0, -6);
+
+ $versions[] = array('name' => $name,
+ 'version' => _('Unknown'));
+
+ return true;
+ }
}
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
index 9fbc9d2dd..fbf9a60ab 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -48,17 +48,17 @@ class PopularNoticeSection extends NoticeSection
{
function getNotices()
{
+ // @fixme there should be a common func for this
if (common_config('db', 'type') == 'pgsql') {
- $weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
if (!empty($this->out->tag)) {
$tag = pg_escape_string($this->out->tag);
}
} else {
- $weightexpr='sum(exp(-(now() - fave.modified) / %s))';
if (!empty($this->out->tag)) {
$tag = mysql_escape_string($this->out->tag);
}
}
+ $weightexpr = common_sql_weight('fave.modified', common_config('popular', 'dropoff'));
$qry = "SELECT notice.*, $weightexpr as weight ";
if(isset($tag)) {
$qry .= 'FROM notice_tag, notice JOIN fave ON notice.id = fave.notice_id ' .
@@ -78,7 +78,7 @@ class PopularNoticeSection extends NoticeSection
$qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
$notice = Memcached_DataObject::cachedQuery('Notice',
- sprintf($qry, common_config('popular', 'dropoff')),
+ $qry,
1200);
return $notice;
}
diff --git a/lib/router.php b/lib/router.php
index 474e05996..287d3c79f 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -100,7 +100,10 @@ class Router
'sandbox', 'unsandbox',
'silence', 'unsilence',
'repeat',
- 'deleteuser');
+ 'deleteuser',
+ 'geocode',
+ 'version',
+ );
foreach ($main as $a) {
$m->connect('main/'.$a, array('action' => $a));
diff --git a/lib/schema.php b/lib/schema.php
index a8ba91b87..a7f64ebed 100644
--- a/lib/schema.php
+++ b/lib/schema.php
@@ -523,6 +523,14 @@ class Schema
} else {
$sql .= ($cd->nullable) ? "null " : "not null ";
}
+
+ if (!empty($cd->auto_increment)) {
+ $sql .= " auto_increment ";
+ }
+
+ if (!empty($cd->extra)) {
+ $sql .= "{$cd->extra} ";
+ }
return $sql;
}
diff --git a/lib/util.php b/lib/util.php
index ed81aeba1..50bd0e2ac 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -62,7 +62,7 @@ function common_init_language()
// gettext will still select the right language.
$language = common_language();
$locale_set = common_init_locale($language);
-
+
setlocale(LC_CTYPE, 'C');
// So we do not have to make people install the gettext locales
$path = common_config('site','locale_path');
@@ -908,6 +908,26 @@ function common_sql_date($datetime)
return strftime('%Y-%m-%d %H:%M:%S', $datetime);
}
+/**
+ * Return an SQL fragment to calculate an age-based weight from a given
+ * timestamp or datetime column.
+ *
+ * @param string $column name of field we're comparing against current time
+ * @param integer $dropoff divisor for age in seconds before exponentiation
+ * @return string SQL fragment
+ */
+function common_sql_weight($column, $dropoff)
+{
+ if (common_config('db', 'type') == 'pgsql') {
+ // PostgreSQL doesn't support timestampdiff function.
+ // @fixme will this use the right time zone?
+ // @fixme does this handle cross-year subtraction correctly?
+ return "sum(exp(-extract(epoch from (now() - $column)) / $dropoff))";
+ } else {
+ return "sum(exp(timestampdiff(second, utc_timestamp(), $column) / $dropoff))";
+ }
+}
+
function common_redirect($url, $code=307)
{
static $status = array(301 => "Moved Permanently",
@@ -1384,41 +1404,17 @@ function common_session_token()
function common_cache_key($extra)
{
- $base_key = common_config('memcached', 'base');
-
- if (empty($base_key)) {
- $base_key = common_keyize(common_config('site', 'name'));
- }
-
- return 'statusnet:' . $base_key . ':' . $extra;
+ return Cache::key($extra);
}
function common_keyize($str)
{
- $str = strtolower($str);
- $str = preg_replace('/\s/', '_', $str);
- return $str;
+ return Cache::keyize($str);
}
function common_memcache()
{
- static $cache = null;
- if (!common_config('memcached', 'enabled')) {
- return null;
- } else {
- if (!$cache) {
- $cache = new Memcache();
- $servers = common_config('memcached', 'server');
- if (is_array($servers)) {
- foreach($servers as $server) {
- $cache->addServer($server);
- }
- } else {
- $cache->addServer($servers);
- }
- }
- return $cache;
- }
+ return Cache::instance();
}
function common_license_terms($uri)