diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/action.php | 4 | ||||
-rw-r--r-- | lib/adminpanelaction.php | 46 | ||||
-rw-r--r-- | lib/authenticationplugin.php | 231 | ||||
-rw-r--r-- | lib/authorizationplugin.php | 105 | ||||
-rw-r--r-- | lib/cache.php | 182 | ||||
-rw-r--r-- | lib/columndef.php | 1 | ||||
-rw-r--r-- | lib/common.php | 33 | ||||
-rw-r--r-- | lib/default.php | 30 | ||||
-rw-r--r-- | lib/grouptagcloudsection.php | 7 | ||||
-rw-r--r-- | lib/htmloutputter.php | 2 | ||||
-rw-r--r-- | lib/jabber.php | 2 | ||||
-rw-r--r-- | lib/jsonsearchresultslist.php | 10 | ||||
-rw-r--r-- | lib/mailhandler.php | 275 | ||||
-rw-r--r-- | lib/noticeform.php | 22 | ||||
-rw-r--r-- | lib/noticelist.php | 10 | ||||
-rw-r--r-- | lib/personaltagcloudsection.php | 11 | ||||
-rw-r--r-- | lib/ping.php | 4 | ||||
-rw-r--r-- | lib/plugin.php | 11 | ||||
-rw-r--r-- | lib/popularnoticesection.php | 6 | ||||
-rw-r--r-- | lib/router.php | 5 | ||||
-rw-r--r-- | lib/schema.php | 8 | ||||
-rw-r--r-- | lib/util.php | 52 |
22 files changed, 982 insertions, 75 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 42d4623b1..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' => @@ -226,9 +224,29 @@ $default = 'message' => array('contentlimit' => null), 'location' => - array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth + array('share' => 'user', // whether to share location; 'always', 'user', 'never' + 'sharedefault' => true), 'omb' => 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) |