Merge branch '0.9.x' into 1.0.x

This commit is contained in:
Brion Vibber 2010-10-07 13:32:26 -07:00
commit 6c959c83ce
13 changed files with 662 additions and 13 deletions

View File

@ -946,7 +946,12 @@ function common_shorten_links($text, $always = false)
function common_validate_utf8($str) function common_validate_utf8($str)
{ {
// preg_replace will return NULL on invalid UTF-8 input. // preg_replace will return NULL on invalid UTF-8 input.
return preg_replace('//u', '', $str); //
// Note: empty regex //u also caused NULL return on some
// production machines, but none of our test machines.
//
// This should be replaced with a more reliable check.
return preg_replace('/\x00/u', '', $str);
} }
/** /**

View File

@ -2,7 +2,7 @@
/** /**
* StatusNet, the distributed open-source microblogging tool * StatusNet, the distributed open-source microblogging tool
* *
* Plugin to push RSS/Atom updates to a PubSubHubBub hub * Plugin to use bit.ly URL shortening services.
* *
* PHP version 5 * PHP version 5
* *
@ -22,7 +22,9 @@
* @category Plugin * @category Plugin
* @package StatusNet * @package StatusNet
* @author Craig Andrews <candrews@integralblue.com> * @author Craig Andrews <candrews@integralblue.com>
* @author Brion Vibber <brion@status.net>
* @copyright 2009 Free Software Foundation, Inc http://www.fsf.org * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org
* @copyright 2010 StatusNet, Inc http://status.net/
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
*/ */
@ -33,26 +35,135 @@ if (!defined('STATUSNET')) {
class BitlyUrlPlugin extends UrlShortenerPlugin class BitlyUrlPlugin extends UrlShortenerPlugin
{ {
public $serviceUrl; public $shortenerName = 'bit.ly';
public $serviceUrl = 'http://bit.ly/api?method=shorten&version=2.0.1&longUrl=%s';
public $login; // To set a site-default when admins or users don't override it.
public $apiKey;
function onInitializePlugin(){ function onInitializePlugin(){
parent::onInitializePlugin(); parent::onInitializePlugin();
if(!isset($this->serviceUrl)){ if(!isset($this->serviceUrl)){
throw new Exception(_m("You must specify a serviceUrl.")); throw new Exception(_m("You must specify a serviceUrl for bit.ly shortening."));
} }
} }
/**
* Add bit.ly to the list of available URL shorteners if it's configured,
* otherwise leave it out.
*
* @param array $shorteners
* @return boolean hook return value
*/
function onGetUrlShorteners(&$shorteners)
{
if ($this->getLogin() && $this->getApiKey()) {
return parent::onGetUrlShorteners($shorteners);
}
return true;
}
/**
* Short a URL
* @param url
* @return string shortened version of the url, or null if URL shortening failed
*/
protected function shorten($url) { protected function shorten($url) {
$response = $this->http_get($url); $response = $this->query($url);
if(!$response) return; if ($this->isOk($url, $response)) {
return current(json_decode($response)->results)->hashUrl; return $this->decode($url, $response->getBody());
} else {
return null;
}
}
/**
* Get the user's or site-wide default bit.ly login name.
*
* @return string
*/
protected function getLogin()
{
$login = common_config('bitly', 'default_login');
if (!$login) {
$login = $this->login;
}
return $login;
}
/**
* Get the user's or site-wide default bit.ly API key.
*
* @return string
*/
protected function getApiKey()
{
$key = common_config('bitly', 'default_apikey');
if (!$key) {
$key = $this->apiKey;
}
return $key;
}
/**
* Inject API key into query before sending out...
*
* @param string $url
* @return HTTPResponse
*/
protected function query($url)
{
// http://code.google.com/p/bitly-api/wiki/ApiDocumentation#/shorten
$params = http_build_query(array(
'login' => $this->getLogin(),
'apiKey' => $this->getApiKey()), '', '&');
$serviceUrl = sprintf($this->serviceUrl, $url) . '&' . $params;
$request = HTTPClient::start();
return $request->get($serviceUrl);
}
/**
* JSON decode for API result
*/
protected function decode($url, $body)
{
$json = json_decode($body, true);
return $json['results'][$url]['shortUrl'];
}
/**
* JSON decode for API result
*/
protected function isOk($url, $response)
{
$code = 'unknown';
$msg = '';
if ($response->isOk()) {
$body = $response->getBody();
common_log(LOG_INFO, $body);
$json = json_decode($body, true);
if ($json['statusCode'] == 'OK') {
$data = $json['results'][$url];
if (isset($data['shortUrl'])) {
return true;
} else if (isset($data['statusCode']) && $data['statusCode'] == 'ERROR') {
$code = $data['errorCode'];
$msg = $data['errorMessage'];
}
} else if ($json['statusCode'] == 'ERROR') {
$code = $json['errorCode'];
$msg = $json['errorMessage'];
}
common_log(LOG_ERR, "bit.ly returned error $code $msg for $url");
}
return false;
} }
function onPluginVersion(&$versions) function onPluginVersion(&$versions)
{ {
$versions[] = array('name' => sprintf('BitlyUrl (%s)', $this->shortenerName), $versions[] = array('name' => sprintf('BitlyUrl (%s)', $this->shortenerName),
'version' => STATUSNET_VERSION, 'version' => STATUSNET_VERSION,
'author' => 'Craig Andrews', 'author' => 'Craig Andrews, Brion Vibber',
'homepage' => 'http://status.net/wiki/Plugin:BitlyUrl', 'homepage' => 'http://status.net/wiki/Plugin:BitlyUrl',
'rawdescription' => 'rawdescription' =>
sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'), sprintf(_m('Uses <a href="http://%1$s/">%1$s</a> URL-shortener service.'),
@ -60,4 +171,85 @@ class BitlyUrlPlugin extends UrlShortenerPlugin
return true; return true;
} }
/**
* Hook for RouterInitialized event.
*
* @param Net_URL_Mapper $m path-to-action mapper
* @return boolean hook return
*/
function onRouterInitialized($m)
{
$m->connect('admin/bitly',
array('action' => 'bitlyadminpanel'));
return true;
}
/**
* If the plugin's installed, this should be accessible to admins.
*/
function onAdminPanelCheck($name, &$isOK)
{
if ($name == 'bitly') {
$isOK = true;
return false;
}
return true;
}
/**
* Add the bit.ly admin panel to the list...
*/
function onEndAdminPanelNav($nav)
{
if (AdminPanelAction::canAdmin('bitly')) {
$action_name = $nav->action->trimmed('action');
$nav->out->menuItem(common_local_url('bitlyadminpanel'),
_m('bit.ly'),
_m('bit.ly URL shortening'),
$action_name == 'bitlyadminpanel',
'nav_bitly_admin_panel');
}
return true;
}
/**
* Automatically load the actions and libraries used by the plugin
*
* @param Class $cls the class
*
* @return boolean hook return
*
*/
function onAutoload($cls)
{
$base = dirname(__FILE__);
$lower = strtolower($cls);
switch ($lower) {
case 'bitlyadminpanelaction':
require_once "$base/$lower.php";
return false;
default:
return true;
}
}
/**
* Internal hook point to check the default global credentials so
* the admin form knows if we have a fallback or not.
*
* @param string $login
* @param string $apiKey
* @return boolean hook return value
*/
function onBitlyDefaultCredentials(&$login, &$apiKey)
{
$login = $this->login;
$apiKey = $this->apiKey;
return false;
}
} }

37
plugins/BitlyUrl/README Normal file
View File

@ -0,0 +1,37 @@
bit.ly URL shortening requires the login name and API key for a bit.ly account.
Register for an account or set up your API key here:
http://bit.ly/a/your_api_key
Administrators can configure a login and API key to use through the admin panels
on the site; these credentials will then be used for all users.
(In the future, options will be added for individual users to override the keys
with their own login for URLs they post.)
If the login and API key are left empty in the admin panel, then bit.ly will be
disabled and hidden from the list of available URL shorteners unless a global
default was provided in the plugin configuration.
To enable bit.ly with no default credentials, simply slip into your config.php:
addPlugin('BitlyUrl');
To provide default credentials, add them as parameters:
addPlugin('BitlyUrl', array(
'login' => 'myname',
'apiKey' => '############################'
));
These settings will not be individually exposed to the admin panels, but the
panel will indicate whether or not the global default settings are available;
this makes it suitable as a global default for multi-site hosting, where admins
on individual sites can change to use their own settings.
If you're using a bit.ly pro account with a custom domain etc, it should all
"just work" as long as you use the correct login name and API key for your
account.

View File

@ -0,0 +1,238 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Admin panel for plugin to use bit.ly URL shortening services.
*
* 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 Settings
* @package StatusNet
* @author Brion Vibber <brion@status.net>
* @copyright 2010 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);
}
/**
* Administer global bit.ly URL shortener settings
*
* @category Admin
* @package StatusNet
* @author Brion Vibber <brion@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 BitlyadminpanelAction extends AdminPanelAction
{
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _m('bit.ly URL shortening');
}
/**
* Instructions for using this form.
*
* @return string instructions
*/
function getInstructions()
{
return _m('URL shortening with bit.ly requires ' .
'[a bit.ly account and API key](http://bit.ly/a/your_api_key). ' .
'This verifies that this is an authorized account, and ' .
'allow you to use bit.ly\'s tracking features and custom domains.');
}
/**
* Show the bit.ly admin panel form
*
* @return void
*/
function showForm()
{
$form = new BitlyAdminPanelForm($this);
$form->show();
return;
}
/**
* Save settings from the form
*
* @return void
*/
function saveSettings()
{
static $settings = array(
'bitly' => array('default_login', 'default_apikey')
);
$values = array();
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
$values[$section][$setting]
= $this->trimmed($setting);
}
}
// This throws an exception on validation errors
$this->validate($values);
// assert(all values are valid);
$config = new Config();
$config->query('BEGIN');
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
Config::save($section, $setting, $values[$section][$setting]);
}
}
$config->query('COMMIT');
return;
}
function validate(&$values)
{
// Validate consumer key and secret (can't be too long)
if (mb_strlen($values['bitly']['default_apikey']) > 255) {
$this->clientError(
_m("Invalid login. Max length is 255 characters.")
);
}
if (mb_strlen($values['bitly']['default_apikey']) > 255) {
$this->clientError(
_m("Invalid API key. Max length is 255 characters.")
);
}
}
}
class BitlyAdminPanelForm extends AdminForm
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'bitlyadminpanel';
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('bitlyadminpanel');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart(
'fieldset',
array('id' => 'settings_bitly')
);
$this->out->element('legend', null, _m('Credentials'));
// Do we have global defaults to fall back on?
$login = $apiKey = false;
Event::handle('BitlyDefaultCredentials', array(&$login, &$apiKey));
$haveGlobalDefaults = ($login && $apiKey);
if ($login && $apiKey) {
$this->out->element('p', 'form_guide',
_m('Leave these empty to use global default credentials.'));
} else {
$this->out->element('p', 'form_guide',
_m('If you leave these empty, bit.ly will be unavailable to users.'));
}
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->input(
'default_login',
_m('Login name'),
null,
'bitly'
);
$this->unli();
$this->li();
$this->input(
'default_apikey',
_m('API key'),
null,
'bitly'
);
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('submit', _('Save'), 'submit', null, _m('Save bit.ly settings'));
}
}

View File

@ -964,7 +964,7 @@ class OStatusPlugin extends Plugin
{ {
$group = User_group::staticGet('uri', $url); $group = User_group::staticGet('uri', $url);
if ($group) { if ($group) {
$local = Local_group::staticGet('id', $group->id); $local = Local_group::staticGet('group_id', $group->id);
if ($local) { if ($local) {
return $group->id; return $group->id;
} }

View File

@ -84,6 +84,50 @@ while ($group->fetch()) {
} }
echo "\n"; echo "\n";
// And there may be user_group entries remaining where we've already killed
// the ostatus_profile. These were "harmless" until our lookup started actually
// using the uri field, at which point we can clearly see it breaks stuff.
echo "Checking for leftover bogus user_group.uri entries obscuring local_group entries...\n";
$group = new User_group();
$group->joinAdd(array('id', 'local_group:group_id'), 'LEFT');
$group->whereAdd('group_id IS NULL');
$marker = mt_rand(31337, 31337000);
$groupTemplate = common_local_url('groupbyid', array('id' => $marker));
$encGroup = $group->escape($groupTemplate, true);
$encGroup = str_replace($marker, '%', $encGroup);
echo " LIKE '$encGroup'\n";
$group->whereAdd("uri LIKE '$encGroup'");
$group->find();
$count = $group->N;
echo "Found $count...\n";
while ($group->fetch()) {
$uri = $group->uri;
if (preg_match('!/group/(\d+)/id!', $uri, $matches)) {
$id = intval($matches[1]);
$local = Local_group::staticGet('group_id', $id);
if ($local) {
$nick = $local->nickname;
} else {
$nick = '<deleted>';
}
echo "local group $id ($local->nickname) hidden by $uri (bogus group id $group->id)";
if ($dry) {
echo " - skipping\n";
} else {
echo " - removing bogus user_group entry...";
$evil = User_group::staticGet('id', $group->id);
$evil->delete();
echo " ok\n";
}
}
}
echo "\n";
// Fallback? // Fallback?
echo "Checking for bogus profiles blocking local users/groups by URI pattern match...\n"; echo "Checking for bogus profiles blocking local users/groups by URI pattern match...\n";

View File

@ -117,7 +117,8 @@ address {
top: 74px; top: 74px;
right:0; right:0;
height: 2.4em; height: 2.4em;
width: 106px; width: 96px;
right: 10px;
} }
#core { #core {

62
theme/clean/css/ie.css Normal file
View File

@ -0,0 +1,62 @@
/* IE specific styles */
/* base theme overrides */
input.checkbox,
input.radio {
top:0;
}
.form_notice textarea {
width: 364px;
}
.form_notice .form_note + label {
position:absolute;
top:25px;
left:83%;
text-indent:-9999px;
height:16px;
width:16px;
display:block;
top: 31px;
right: 88px;
}
.form_notice #notice_action-submit {
width: 96px;
max-width: 96px;
}
.form_notice #notice_data-attach_selected,
.form_notice #notice_data-geo_selected {
width:78.75%;
}
.form_notice #notice_data-attach_selected button,
.form_notice #notice_data-geo_selected button {
padding:0 4px;
}
.notice-options input.submit {
font-size:0;
text-align:right;
text-indent:0;
}
.notice div.entry-content .timestamp a {
margin-right:4px;
}
.entity_profile {
width:64%;
}
.notice {
z-index:1;
}
.notice:hover {
z-index:9999;
}
.notice .thumbnail img {
z-index:9999;
}
.form_settings fieldset fieldset legend {
line-height:auto;
}
.form_notice #notice_data-attach {
filter: alpha(opacity=0);
}

View File

@ -196,6 +196,7 @@ address .poweredby {
width: 485px; width: 485px;
height: 63px; height: 63px;
padding-bottom: 15px; padding-bottom: 15px;
z-index: 9;
} }
.form_notice label[for=notice_data-attach], .form_notice label[for=notice_data-attach],

View File

@ -58,3 +58,8 @@ z-index:9999;
.form_settings fieldset fieldset legend { .form_settings fieldset fieldset legend {
line-height:auto; line-height:auto;
} }
.form_notice #notice_data-attach {
filter: alpha(opacity=0);
}

View File

@ -209,7 +209,8 @@ address {
top: 74px; top: 74px;
right:0; right:0;
height: 2.4em; height: 2.4em;
width: 106px; width: 96px;
right: 10px;
} }
#content { #content {

View File

@ -1,9 +1,72 @@
/* IE specific styles */ /* IE specific styles */
/* IE specific styles */
/* base theme overrides */
input.checkbox,
input.radio {
top:0;
}
.form_notice textarea {
width: 374px;
}
.form_notice .form_note + label {
position:absolute;
top:25px;
left:83%;
text-indent:-9999px;
height:16px;
width:16px;
display:block;
top: 31px;
right: 88px;
}
.form_notice #notice_action-submit {
width: 96px;
max-width: 96px;
}
.form_notice #notice_data-attach_selected,
.form_notice #notice_data-geo_selected {
width:78.75%;
}
.form_notice #notice_data-attach_selected button,
.form_notice #notice_data-geo_selected button {
padding:0 4px;
}
.notice-options input.submit {
font-size:0;
text-align:right;
text-indent:0;
}
.notice div.entry-content .timestamp a {
margin-right:4px;
}
.entity_profile {
width:64%;
}
.notice {
z-index:1;
}
.notice:hover {
z-index:9999;
}
.notice .thumbnail img {
z-index:9999;
}
.form_settings fieldset fieldset legend {
line-height:auto;
}
.form_notice #notice_data-attach {
filter: alpha(opacity=0);
}
#wrap { #wrap {
background-color: #c9c9c9; background: url(../images/wrap_bg.png) repeat top left;
} }
#aside_primary .section { #aside_primary .section {
background-color: #c9c9c9; background: url(../images/wrap_bg.png) repeat top left;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B