Merge branch 'testing' into 0.9.x

This commit is contained in:
Brion Vibber 2010-04-09 08:40:15 -07:00
commit 1f8451f4aa
16 changed files with 1392 additions and 930 deletions

View File

@ -56,7 +56,7 @@ class FoafGroupAction extends Action
return false; return false;
} }
$local = Local_group::staticGet('nickname', $nickname); $local = Local_group::staticGet('nickname', $this->nickname);
if (!$local) { if (!$local) {
$this->clientError(_('No such group.'), 404); $this->clientError(_('No such group.'), 404);
@ -126,7 +126,8 @@ class FoafGroupAction extends Action
while ($members->fetch()) { while ($members->fetch()) {
$member_uri = common_local_url('userbyid', array('id'=>$members->id)); $member_uri = common_local_url('userbyid', array('id'=>$members->id));
$member_details[$member_uri] = array( $member_details[$member_uri] = array(
'nickname' => $members->nickname 'nickname' => $members->nickname,
'is_admin' => false,
); );
$this->element('member', array('rdf:resource' => $member_uri)); $this->element('member', array('rdf:resource' => $member_uri));
} }

View File

@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @link http://status.net/ * @link http://status.net/
*/ */
class GroupblockAction extends Action class GroupblockAction extends RedirectingAction
{ {
var $profile = null; var $profile = null;
var $group = null; var $group = null;
@ -117,9 +117,7 @@ class GroupblockAction extends Action
parent::handle($args); parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($this->arg('no')) { if ($this->arg('no')) {
common_redirect(common_local_url('groupmembers', $this->returnToArgs();
array('nickname' => $this->group->nickname)),
303);
} elseif ($this->arg('yes')) { } elseif ($this->arg('yes')) {
$this->blockProfile(); $this->blockProfile();
} elseif ($this->arg('blockto')) { } elseif ($this->arg('blockto')) {
@ -197,22 +195,19 @@ class GroupblockAction extends Action
return false; return false;
} }
// Now, gotta figure where we go back to $this->returnToArgs();
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} elseif (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
} }
if ($action) { /**
common_redirect(common_local_url($action, $args), 303); * If we reached this form without returnto arguments, default to
} else { * the top of the group's member list.
common_redirect(common_local_url('groupmembers', *
array('nickname' => $this->group->nickname)), * @return string URL
303); */
} function defaultReturnTo()
{
return common_local_url('groupmembers',
array('nickname' => $this->group->nickname));
} }
function showScripts() function showScripts()

View File

@ -205,8 +205,7 @@ class GroupMemberListItem extends ProfileListItem
!$this->profile->isAdmin($this->group)) { !$this->profile->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_make_admin'); $this->out->elementStart('li', 'entity_make_admin');
$maf = new MakeAdminForm($this->out, $this->profile, $this->group, $maf = new MakeAdminForm($this->out, $this->profile, $this->group,
array('action' => 'groupmembers', $this->returnToArgs());
'nickname' => $this->group->nickname));
$maf->show(); $maf->show();
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} }
@ -220,8 +219,7 @@ class GroupMemberListItem extends ProfileListItem
if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
$this->out->elementStart('li', 'entity_block'); $this->out->elementStart('li', 'entity_block');
$bf = new GroupBlockForm($this->out, $this->profile, $this->group, $bf = new GroupBlockForm($this->out, $this->profile, $this->group,
array('action' => 'groupmembers', $this->returnToArgs());
'nickname' => $this->group->nickname));
$bf->show(); $bf->show();
$this->out->elementEnd('li'); $this->out->elementEnd('li');
} }
@ -244,6 +242,23 @@ class GroupMemberListItem extends ProfileListItem
$aAttrs['rel'] = 'nofollow'; $aAttrs['rel'] = 'nofollow';
} }
} }
/**
* Fetch necessary return-to arguments for the profile forms
* to return to this list when they're done.
*
* @return array
*/
protected function returnToArgs()
{
$args = array('action' => 'groupmembers',
'nickname' => $this->group->nickname);
$page = $this->out->arg('page');
if ($page) {
$args['param-page'] = $page;
}
return $args;
}
} }
/** /**

View File

@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @link http://status.net/ * @link http://status.net/
*/ */
class MakeadminAction extends Action class MakeadminAction extends RedirectingAction
{ {
var $profile = null; var $profile = null;
var $group = null; var $group = null;
@ -148,20 +148,19 @@ class MakeadminAction extends Action
$this->group->getBestName()); $this->group->getBestName());
} }
foreach ($this->args as $k => $v) { $this->returnToArgs();
if ($k == 'returnto-action') {
$action = $v;
} else if (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
} }
if ($action) { /**
common_redirect(common_local_url($action, $args), 303); * If we reached this form without returnto arguments, default to
} else { * the top of the group's member list.
common_redirect(common_local_url('groupmembers', *
array('nickname' => $this->group->nickname)), * @return string URL
303); */
} function defaultReturnTo()
{
return common_local_url('groupmembers',
array('nickname' => $this->group->nickname));
} }
} }

View File

@ -157,9 +157,13 @@ class SubscribersListItem extends SubscriptionListItem
$user = common_current_user(); $user = common_current_user();
if (!empty($user) && $this->owner->id == $user->id) { if (!empty($user) && $this->owner->id == $user->id) {
$bf = new BlockForm($this->out, $this->profile, $returnto = array('action' => 'subscribers',
array('action' => 'subscribers', 'nickname' => $this->owner->nickname);
'nickname' => $this->owner->nickname)); $page = $this->out->arg('page');
if ($page) {
$returnto['param-page'] = $page;
}
$bf = new BlockForm($this->out, $this->profile, $returnto);
$bf->show(); $bf->show();
} }
} }

View File

@ -1,8 +1,7 @@
<?php <?php
/** /**
* StatusNet - the distributed open-source microblogging tool * StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc. * Copyright (C) 2009-2010, StatusNet, Inc.
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -39,414 +38,7 @@
define('INSTALLDIR', dirname(__FILE__)); define('INSTALLDIR', dirname(__FILE__));
$external_libraries=array( require INSTALLDIR . '/lib/installer.php';
array(
'name'=>'gettext',
'url'=>'http://us.php.net/manual/en/book.gettext.php',
'check_function'=>'gettext'
),
array(
'name'=>'PEAR',
'url'=>'http://pear.php.net/',
'deb'=>'php-pear',
'include'=>'PEAR.php',
'check_class'=>'PEAR'
),
array(
'name'=>'DB',
'pear'=>'DB',
'url'=>'http://pear.php.net/package/DB',
'deb'=>'php-db',
'include'=>'DB/common.php',
'check_class'=>'DB_common'
),
array(
'name'=>'DB_DataObject',
'pear'=>'DB_DataObject',
'url'=>'http://pear.php.net/package/DB_DataObject',
'include'=>'DB/DataObject.php',
'check_class'=>'DB_DataObject'
),
array(
'name'=>'Console_Getopt',
'pear'=>'Console_Getopt',
'url'=>'http://pear.php.net/package/Console_Getopt',
'include'=>'Console/Getopt.php',
'check_class'=>'Console_Getopt'
),
array(
'name'=>'Facebook API',
'url'=>'http://developers.facebook.com/',
'include'=>'facebook/facebook.php',
'check_class'=>'Facebook'
),
array(
'name'=>'htmLawed',
'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
'include'=>'htmLawed/htmLawed.php',
'check_function'=>'htmLawed'
),
array(
'name'=>'HTTP_Request',
'pear'=>'HTTP_Request',
'url'=>'http://pear.php.net/package/HTTP_Request',
'deb'=>'php-http-request',
'include'=>'HTTP/Request.php',
'check_class'=>'HTTP_Request'
),
array(
'name'=>'HTTP_Request2',
'pear'=>'HTTP_Request2',
'url'=>'http://pear.php.net/package/HTTP_Request2',
'include'=>'HTTP/Request2.php',
'check_class'=>'HTTP_Request2'
),
array(
'name'=>'Mail',
'pear'=>'Mail',
'url'=>'http://pear.php.net/package/Mail',
'deb'=>'php-mail',
'include'=>'Mail.php',
'check_class'=>'Mail'
),
array(
'name'=>'Mail_mimeDecode',
'pear'=>'Mail_mimeDecode',
'url'=>'http://pear.php.net/package/Mail_mimeDecode',
'deb'=>'php-mail-mimedecode',
'include'=>'Mail/mimeDecode.php',
'check_class'=>'Mail_mimeDecode'
),
array(
'name'=>'Mime_Type',
'pear'=>'Mime_Type',
'url'=>'http://pear.php.net/package/Mime_Type',
'include'=>'MIME/Type.php',
'check_class'=>'Mime_Type'
),
array(
'name'=>'Net_URL_Mapper',
'pear'=>'Net_URL_Mapper',
'url'=>'http://pear.php.net/package/Net_URL_Mapper',
'include'=>'Net/URL/Mapper.php',
'check_class'=>'Net_URL_Mapper'
),
array(
'name'=>'Net_LDAP2',
'pear'=>'Net_LDAP2',
'url'=>'http://pear.php.net/package/Net_LDAP2',
'deb'=>'php-net-ldap2',
'include'=>'Net/LDAP2.php',
'check_class'=>'Net_LDAP2'
),
array(
'name'=>'Net_Socket',
'pear'=>'Net_Socket',
'url'=>'http://pear.php.net/package/Net_Socket',
'deb'=>'php-net-socket',
'include'=>'Net/Socket.php',
'check_class'=>'Net_Socket'
),
array(
'name'=>'Net_SMTP',
'pear'=>'Net_SMTP',
'url'=>'http://pear.php.net/package/Net_SMTP',
'deb'=>'php-net-smtp',
'include'=>'Net/SMTP.php',
'check_class'=>'Net_SMTP'
),
array(
'name'=>'Net_URL',
'pear'=>'Net_URL',
'url'=>'http://pear.php.net/package/Net_URL',
'deb'=>'php-net-url',
'include'=>'Net/URL.php',
'check_class'=>'Net_URL'
),
array(
'name'=>'Net_URL2',
'pear'=>'Net_URL2',
'url'=>'http://pear.php.net/package/Net_URL2',
'include'=>'Net/URL2.php',
'check_class'=>'Net_URL2'
),
array(
'name'=>'Services_oEmbed',
'pear'=>'Services_oEmbed',
'url'=>'http://pear.php.net/package/Services_oEmbed',
'include'=>'Services/oEmbed.php',
'check_class'=>'Services_oEmbed'
),
array(
'name'=>'Stomp',
'url'=>'http://stomp.codehaus.org/PHP',
'include'=>'Stomp.php',
'check_class'=>'Stomp'
),
array(
'name'=>'System_Command',
'pear'=>'System_Command',
'url'=>'http://pear.php.net/package/System_Command',
'include'=>'System/Command.php',
'check_class'=>'System_Command'
),
array(
'name'=>'XMPPHP',
'url'=>'http://code.google.com/p/xmpphp',
'include'=>'XMPPHP/XMPP.php',
'check_class'=>'XMPPHP_XMPP'
),
array(
'name'=>'PHP Markdown',
'url'=>'http://www.michelf.com/projects/php-markdown/',
'include'=>'markdown.php',
'check_class'=>'Markdown_Parser'
),
array(
'name'=>'OAuth',
'url'=>'http://code.google.com/p/oauth-php',
'include'=>'OAuth.php',
'check_class'=>'OAuthRequest'
),
array(
'name'=>'Validate',
'pear'=>'Validate',
'url'=>'http://pear.php.net/package/Validate',
'include'=>'Validate.php',
'check_class'=>'Validate'
)
);
$dbModules = array(
'mysql' => array(
'name' => 'MySQL',
'check_module' => 'mysql', // mysqli?
'installer' => 'mysql_db_installer',
),
'pgsql' => array(
'name' => 'PostgreSQL',
'check_module' => 'pgsql',
'installer' => 'pgsql_db_installer',
),
);
/**
* the actual installation.
* If call libraries are present, then install
*
* @return void
*/
function main()
{
if (!checkPrereqs()) {
return;
}
if (!empty($_GET['checklibs'])) {
showLibs();
} else {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
handlePost();
} else {
showForm();
}
}
}
/**
* checks if an external libary is present
*
* @param string $external_library Name of library
*
* @return boolean indicates if library present
*/
function haveExternalLibrary($external_library)
{
if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
return false;
}
if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
return false;
}
if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
return false;
}
return true;
}
// Attempt to include a PHP file and report if it worked, while
// suppressing the annoying warning messages on failure.
function haveIncludeFile($filename) {
$old = error_reporting(error_reporting() & ~E_WARNING);
$ok = include_once($filename);
error_reporting($old);
return $ok;
}
/**
* Check if all is ready for installation
*
* @return void
*/
function checkPrereqs()
{
$pass = true;
if (file_exists(INSTALLDIR.'/config.php')) {
printf('<p class="error">Config file &quot;config.php&quot; already exists.</p>');
$pass = false;
}
if (version_compare(PHP_VERSION, '5.2.3', '<')) {
printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
$pass = false;
}
// Look for known library bugs
$str = "abcdefghijklmnopqrstuvwxyz";
$replaced = preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
if ($str != $replaced) {
printf('<p class="error">PHP is linked to a version of the PCRE library ' .
'that does not support Unicode properties. ' .
'If you are running Red Hat Enterprise Linux / ' .
'CentOS 5.4 or earlier, see <a href="' .
'http://status.net/wiki/Red_Hat_Enterprise_Linux#PCRE_library' .
'">our documentation page</a> on fixing this.</p>');
$pass = false;
}
$reqs = array('gd', 'curl',
'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
foreach ($reqs as $req) {
if (!checkExtension($req)) {
printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
$pass = false;
}
}
// Make sure we have at least one database module available
global $dbModules;
$missingExtensions = array();
foreach ($dbModules as $type => $info) {
if (!checkExtension($info['check_module'])) {
$missingExtensions[] = $info['check_module'];
}
}
if (count($missingExtensions) == count($dbModules)) {
$req = implode(', ', $missingExtensions);
printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
$pass = false;
}
if (!is_writable(INSTALLDIR)) {
printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
$pass = false;
}
// Check the subdirs used for file uploads
$fileSubdirs = array('avatar', 'background', 'file');
foreach ($fileSubdirs as $fileSubdir) {
$fileFullPath = INSTALLDIR."/$fileSubdir/";
if (!is_writable($fileFullPath)) {
printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
$pass = false;
}
}
return $pass;
}
/**
* Checks if a php extension is both installed and loaded
*
* @param string $name of extension to check
*
* @return boolean whether extension is installed and loaded
*/
function checkExtension($name)
{
if (extension_loaded($name)) {
return true;
} elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
// dl will throw a fatal error if it's disabled or we're in safe mode.
// More fun, it may not even exist under some SAPIs in 5.3.0 or later...
$soname = $name . '.' . PHP_SHLIB_SUFFIX;
if (PHP_SHLIB_SUFFIX == 'dll') {
$soname = "php_" . $soname;
}
return @dl($soname);
} else {
return false;
}
}
/**
* Show list of libraries
*
* @return void
*/
function showLibs()
{
global $external_libraries;
$present_libraries=array();
$absent_libraries=array();
foreach ($external_libraries as $external_library) {
if (haveExternalLibrary($external_library)) {
$present_libraries[]=$external_library;
} else {
$absent_libraries[]=$external_library;
}
}
echo<<<E_O_T
<div class="instructions">
<p>StatusNet comes bundled with a number of libraries required for the application to work. However, it is best that you use PEAR or you distribution to manage
libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
<p>On Debian based distributions, such as Ubuntu, use a package manager (such as &quot;aptitude&quot;, &quot;apt-get&quot;, and &quot;synaptic&quot;) to install the package listed.</p>
<p>On RPM based distributions, such as Red Hat, Fedora, CentOS, Scientific Linux, Yellow Dog Linux and Oracle Enterprise Linux, use a package manager (such as &quot;yum&quot;, &quot;apt-rpm&quot;, and &quot;up2date&quot;) to install the package listed.</p>
<p>On servers without a package manager (such as Windows), or if the library is not packaged for your distribution, you can use PHP's PEAR to install the library. Simply run &quot;pear install &lt;name&gt;&quot;.</p>
</div>
<h2>Absent Libraries</h2>
<ul id="absent_libraries">
E_O_T;
foreach ($absent_libraries as $library) {
echo '<li>';
if (isset($library['url'])) {
echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
} else {
echo htmlentities($library['name']);
}
echo '<ul>';
if (isset($library['deb'])) {
echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
}
if (isset($library['rpm'])) {
echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
}
if (isset($library['pear'])) {
echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
}
echo '</ul>';
}
echo<<<E_O_T
</ul>
<h2>Installed Libraries</h2>
<ul id="present_libraries">
E_O_T;
foreach ($present_libraries as $library) {
echo '<li>';
if (isset($library['url'])) {
echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
} else {
echo htmlentities($library['name']);
}
echo '</li>';
}
echo<<<E_O_T
</ul>
E_O_T;
}
/** /**
* Helper class for building form * Helper class for building form
@ -462,6 +54,53 @@ class Posted {
} }
} }
/**
* Web-based installer: provides a form and such.
*/
class WebInstaller extends Installer
{
/**
* the actual installation.
* If call libraries are present, then install
*
* @return void
*/
function main()
{
if (!$this->checkPrereqs()) {
$this->showForm();
return;
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
} else {
$this->showForm();
}
}
/**
* Web implementation of warning output
*/
function warning($message, $submessage='')
{
print "<p class=\"error\">$message</p>\n";
if ($submessage != '') {
print "<p>$submessage</p>\n";
}
}
/**
* Web implementation of status output
*/
function updateStatus($status, $error=false)
{
echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
}
/**
* Show the web form!
*/
function showForm() function showForm()
{ {
global $dbModules; global $dbModules;
@ -472,8 +111,8 @@ function showForm()
} else { } else {
$dbtype = null; $dbtype = null;
} }
foreach ($dbModules as $type => $info) { foreach (self::$dbModules as $type => $info) {
if (checkExtension($info['check_module'])) { if ($this->checkExtension($info['check_module'])) {
if ($dbtype == null || $dbtype == $type) { if ($dbtype == null || $dbtype == $type) {
$checked = 'checked="checked" '; $checked = 'checked="checked" ';
$dbtype = $type; // if we didn't have one checked, hit the first $dbtype = $type; // if we didn't have one checked, hit the first
@ -485,9 +124,6 @@ function showForm()
} }
echo<<<E_O_T echo<<<E_O_T
</ul>
</dd>
</dl>
<form method="post" action="install.php" class="form_settings" id="form_install"> <form method="post" action="install.php" class="form_settings" id="form_install">
<fieldset> <fieldset>
<fieldset id="settings_site"> <fieldset id="settings_site">
@ -574,345 +210,74 @@ function showForm()
E_O_T; E_O_T;
} }
function updateStatus($status, $error=false) /**
{ * Handle a POST submission... if we have valid input, start the install!
echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>"; * Otherwise shows the form along with any error messages.
} */
function handlePost() function handlePost()
{ {
$host = $_POST['host'];
$dbtype = $_POST['dbtype'];
$database = $_POST['database'];
$username = $_POST['dbusername'];
$password = $_POST['dbpassword'];
$sitename = $_POST['sitename'];
$fancy = !empty($_POST['fancy']);
$adminNick = strtolower($_POST['admin_nickname']);
$adminPass = $_POST['admin_password'];
$adminPass2 = $_POST['admin_password2'];
$adminEmail = $_POST['admin_email'];
$adminUpdates = $_POST['admin_updates'];
$server = $_SERVER['HTTP_HOST'];
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
echo <<<STR echo <<<STR
<dl class="system_notice"> <dl class="system_notice">
<dt>Page notice</dt> <dt>Page notice</dt>
<dd> <dd>
<ul> <ul>
STR; STR;
$fail = false; $this->validated = $this->prepare();
if ($this->validated) {
if (empty($host)) { $this->doInstall();
updateStatus("No hostname specified.", true);
$fail = true;
} }
echo <<<STR
if (empty($database)) { </ul>
updateStatus("No database specified.", true); </dd>
$fail = true; </dl>
STR;
if (!$this->validated) {
$this->showForm();
} }
if (empty($username)) {
updateStatus("No username specified.", true);
$fail = true;
}
if (empty($sitename)) {
updateStatus("No sitename specified.", true);
$fail = true;
}
if (empty($adminNick)) {
updateStatus("No initial StatusNet user nickname specified.", true);
$fail = true;
}
if ($adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $adminNick)) {
updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
'" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
$fail = true;
}
// @fixme hardcoded list; should use User::allowed_nickname()
// if/when it's safe to have loaded the infrastructure here
$blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
if (in_array($adminNick, $blacklist)) {
updateStatus('The user nickname "' . htmlspecialchars($adminNick) .
'" is reserved.', true);
$fail = true;
}
if (empty($adminPass)) {
updateStatus("No initial StatusNet user password specified.", true);
$fail = true;
}
if ($adminPass != $adminPass2) {
updateStatus("Administrator passwords do not match. Did you mistype?", true);
$fail = true;
}
if ($fail) {
showForm();
return;
}
global $dbModules;
$db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
if (!$db) {
// database connection failed, do not move on to create config file.
return false;
}
updateStatus("Writing config file...");
$res = writeConf($sitename, $server, $path, $fancy, $db);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
// Okay, cross fingers and try to register an initial user
if (registerInitialUser($adminNick, $adminPass, $adminEmail, $adminUpdates)) {
updateStatus(
"An initial user with the administrator role has been created."
);
} else {
updateStatus(
"Could not create initial StatusNet user (administrator).",
true
);
showForm();
return;
}
/*
TODO https needs to be considered
*/
$link = "http://".$server.'/'.$path;
updateStatus("StatusNet has been installed at $link");
updateStatus(
"<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>."
);
}
function Pgsql_Db_installer($host, $database, $username, $password)
{
$connstring = "dbname=$database host=$host user=$username";
//No password would mean trust authentication used.
if (!empty($password)) {
$connstring .= " password=$password";
}
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = pg_connect($connstring);
if ($conn ===false) {
updateStatus("Failed to connect to database: $connstring");
showForm();
return false;
}
//ensure database encoding is UTF8
$record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
if ($record->server_encoding != 'UTF8') {
updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
showForm();
return false;
}
updateStatus("Running database script...");
//wrap in transaction;
pg_query($conn, 'BEGIN');
$res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
updateStatus(sprintf("Adding %s data to database...", $name));
$res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return false;
}
}
pg_query($conn, 'COMMIT');
if (empty($password)) {
$sqlUrl = "pgsql://$username@$host/$database";
} else {
$sqlUrl = "pgsql://$username:$password@$host/$database";
}
$db = array('type' => 'pgsql', 'database' => $sqlUrl);
return $db;
}
function Mysql_Db_installer($host, $database, $username, $password)
{
updateStatus("Starting installation...");
updateStatus("Checking database...");
$conn = mysql_connect($host, $username, $password);
if (!$conn) {
updateStatus("Can't connect to server '$host' as '$username'.", true);
showForm();
return false;
}
updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
updateStatus("Can't change to database.", true);
showForm();
return false;
}
updateStatus("Running database script...");
$res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
updateStatus(sprintf("Adding %s data to database...", $name));
$res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return false;
}
}
$sqlUrl = "mysqli://$username:$password@$host/$database";
$db = array('type' => 'mysql', 'database' => $sqlUrl);
return $db;
}
function writeConf($sitename, $server, $path, $fancy, $db)
{
// assemble configuration file in a string
$cfg = "<?php\n".
"if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
// site name
"\$config['site']['name'] = '$sitename';\n\n".
// site location
"\$config['site']['server'] = '$server';\n".
"\$config['site']['path'] = '$path'; \n\n".
// checks if fancy URLs are enabled
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
// database
"\$config['db']['database'] = '{$db['database']}';\n\n".
($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
"\$config['db']['type'] = '{$db['type']}';\n\n";
// write configuration file out to install directory
$res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
return $res;
} }
/** /**
* Install schema into the database * Read and validate input data.
* May output side effects.
* *
* @param string $filename location of database schema file * @return boolean success
* @param dbconn $conn connection to database
* @param string $type type of database, currently mysql or pgsql
*
* @return boolean - indicating success or failure
*/ */
function runDbScript($filename, $conn, $type = 'mysqli') function prepare()
{ {
$sql = trim(file_get_contents($filename)); $this->host = $_POST['host'];
$stmts = explode(';', $sql); $this->dbtype = $_POST['dbtype'];
foreach ($stmts as $stmt) { $this->database = $_POST['database'];
$stmt = trim($stmt); $this->username = $_POST['dbusername'];
if (!mb_strlen($stmt)) { $this->password = $_POST['dbpassword'];
continue; $this->sitename = $_POST['sitename'];
} $this->fancy = !empty($_POST['fancy']);
// FIXME: use PEAR::DB or PDO instead of our own switch
switch ($type) { $this->adminNick = strtolower($_POST['admin_nickname']);
case 'mysqli': $this->adminPass = $_POST['admin_password'];
$res = mysql_query($stmt, $conn); $adminPass2 = $_POST['admin_password2'];
if ($res === false) { $this->adminEmail = $_POST['admin_email'];
$error = mysql_error(); $this->adminUpdates = $_POST['admin_updates'];
}
break; $this->server = $_SERVER['HTTP_HOST'];
case 'pgsql': $this->path = substr(dirname($_SERVER['PHP_SELF']), 1);
$res = pg_query($conn, $stmt);
if ($res === false) { $fail = false;
$error = pg_last_error(); if (!$this->validateDb()) {
} $fail = true;
break;
default:
updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
}
if ($res === false) {
updateStatus("ERROR ($error) for SQL '$stmt'");
return $res;
}
}
return true;
} }
function registerInitialUser($nickname, $password, $email, $adminUpdates) if (!$this->validateAdmin()) {
{ $fail = true;
define('STATUSNET', true);
define('LACONICA', true); // compatibility
require_once INSTALLDIR . '/lib/common.php';
$data = array('nickname' => $nickname,
'password' => $password,
'fullname' => $nickname);
if ($email) {
$data['email'] = $email;
}
$user = User::register($data);
if (empty($user)) {
return false;
} }
// give initial user carte blanche if ($this->adminPass != $adminPass2) {
$this->updateStatus("Administrator passwords do not match. Did you mistype?", true);
$user->grantRole('owner'); $fail = true;
$user->grantRole('moderator'); }
$user->grantRole('administrator');
return !$fail;
// Attempt to do a remote subscribe to update@status.net
// Will fail if instance is on a private network.
if (class_exists('Ostatus_profile') && $adminUpdates) {
try {
$oprofile = Ostatus_profile::ensureProfileURL('http://update.status.net/');
Subscription::start($user->getProfile(), $oprofile->localProfile());
updateStatus("Set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
} catch (Exception $e) {
updateStatus("Could not set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
}
} }
return true;
} }
?> ?>
@ -945,7 +310,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<div id="content"> <div id="content">
<div id="content_inner"> <div id="content_inner">
<h1>Install StatusNet</h1> <h1>Install StatusNet</h1>
<?php main(); ?> <?php
$installer = new WebInstaller();
$installer->main();
?>
</div> </div>
</div> </div>
</div> </div>

578
lib/installer.php Normal file
View File

@ -0,0 +1,578 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Installation
* @package Installation
*
* @author Adrian Lang <mail@adrianlang.de>
* @author Brenda Wallace <shiny@cpan.org>
* @author Brett Taylor <brett@webfroot.co.nz>
* @author Brion Vibber <brion@pobox.com>
* @author CiaranG <ciaran@ciarang.com>
* @author Craig Andrews <candrews@integralblue.com>
* @author Eric Helgeson <helfire@Erics-MBP.local>
* @author Evan Prodromou <evan@status.net>
* @author Robin Millette <millette@controlyourself.ca>
* @author Sarven Capadisli <csarven@status.net>
* @author Tom Adams <tom@holizz.com>
* @author Zach Copley <zach@status.net>
* @license GNU Affero General Public License http://www.gnu.org/licenses/
* @version 0.9.x
* @link http://status.net
*/
abstract class Installer
{
/** Web site info */
public $sitename, $server, $path, $fancy;
/** DB info */
public $host, $dbname, $dbtype, $username, $password, $db;
/** Administrator info */
public $adminNick, $adminPass, $adminEmail, $adminUpdates;
/** Should we skip writing the configuration file? */
public $skipConfig = false;
public static $dbModules = array(
'mysql' => array(
'name' => 'MySQL',
'check_module' => 'mysql', // mysqli?
'installer' => 'mysql_db_installer',
),
'pgsql' => array(
'name' => 'PostgreSQL',
'check_module' => 'pgsql',
'installer' => 'pgsql_db_installer',
),
);
/**
* Attempt to include a PHP file and report if it worked, while
* suppressing the annoying warning messages on failure.
*/
private function haveIncludeFile($filename) {
$old = error_reporting(error_reporting() & ~E_WARNING);
$ok = include_once($filename);
error_reporting($old);
return $ok;
}
/**
* Check if all is ready for installation
*
* @return void
*/
function checkPrereqs()
{
$pass = true;
if (file_exists(INSTALLDIR.'/config.php')) {
$this->warning('Config file "config.php" already exists.');
$pass = false;
}
if (version_compare(PHP_VERSION, '5.2.3', '<')) {
$errors[] = 'Require PHP version 5.2.3 or greater.';
$pass = false;
}
// Look for known library bugs
$str = "abcdefghijklmnopqrstuvwxyz";
$replaced = preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
if ($str != $replaced) {
$this->warning('PHP is linked to a version of the PCRE library ' .
'that does not support Unicode properties. ' .
'If you are running Red Hat Enterprise Linux / ' .
'CentOS 5.4 or earlier, see <a href="' .
'http://status.net/wiki/Red_Hat_Enterprise_Linux#PCRE_library' .
'">our documentation page</a> on fixing this.');
$pass = false;
}
$reqs = array('gd', 'curl',
'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
foreach ($reqs as $req) {
if (!$this->checkExtension($req)) {
$this->warning(sprintf('Cannot load required extension: <code>%s</code>', $req));
$pass = false;
}
}
// Make sure we have at least one database module available
$missingExtensions = array();
foreach (self::$dbModules as $type => $info) {
if (!$this->checkExtension($info['check_module'])) {
$missingExtensions[] = $info['check_module'];
}
}
if (count($missingExtensions) == count(self::$dbModules)) {
$req = implode(', ', $missingExtensions);
$this->warning(sprintf('Cannot find a database extension. You need at least one of %s.', $req));
$pass = false;
}
if (!is_writable(INSTALLDIR)) {
$this->warning(sprintf('Cannot write config file to: <code>%s</code></p>', INSTALLDIR),
sprintf('On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR));
$pass = false;
}
// Check the subdirs used for file uploads
$fileSubdirs = array('avatar', 'background', 'file');
foreach ($fileSubdirs as $fileSubdir) {
$fileFullPath = INSTALLDIR."/$fileSubdir/";
if (!is_writable($fileFullPath)) {
$this->warning(sprintf('Cannot write to %s directory: <code>%s</code>', $fileSubdir, $fileFullPath),
sprintf('On your server, try this command: <code>chmod a+w %s</code>', $fileFullPath));
$pass = false;
}
}
return $pass;
}
/**
* Checks if a php extension is both installed and loaded
*
* @param string $name of extension to check
*
* @return boolean whether extension is installed and loaded
*/
function checkExtension($name)
{
if (extension_loaded($name)) {
return true;
} elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
// dl will throw a fatal error if it's disabled or we're in safe mode.
// More fun, it may not even exist under some SAPIs in 5.3.0 or later...
$soname = $name . '.' . PHP_SHLIB_SUFFIX;
if (PHP_SHLIB_SUFFIX == 'dll') {
$soname = "php_" . $soname;
}
return @dl($soname);
} else {
return false;
}
}
/**
* Basic validation on the database paramters
* Side effects: error output if not valid
*
* @return boolean success
*/
function validateDb()
{
$fail = false;
if (empty($this->host)) {
$this->updateStatus("No hostname specified.", true);
$fail = true;
}
if (empty($this->database)) {
$this->updateStatus("No database specified.", true);
$fail = true;
}
if (empty($this->username)) {
$this->updateStatus("No username specified.", true);
$fail = true;
}
if (empty($this->sitename)) {
$this->updateStatus("No sitename specified.", true);
$fail = true;
}
return !$fail;
}
/**
* Basic validation on the administrator user paramters
* Side effects: error output if not valid
*
* @return boolean success
*/
function validateAdmin()
{
$fail = false;
if (empty($this->adminNick)) {
$this->updateStatus("No initial StatusNet user nickname specified.", true);
$fail = true;
}
if ($this->adminNick && !preg_match('/^[0-9a-z]{1,64}$/', $this->adminNick)) {
$this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
'" is invalid; should be plain letters and numbers no longer than 64 characters.', true);
$fail = true;
}
// @fixme hardcoded list; should use User::allowed_nickname()
// if/when it's safe to have loaded the infrastructure here
$blacklist = array('main', 'admin', 'twitter', 'settings', 'rsd.xml', 'favorited', 'featured', 'favoritedrss', 'featuredrss', 'rss', 'getfile', 'api', 'groups', 'group', 'peopletag', 'tag', 'user', 'message', 'conversation', 'bookmarklet', 'notice', 'attachment', 'search', 'index.php', 'doc', 'opensearch', 'robots.txt', 'xd_receiver.html', 'facebook');
if (in_array($this->adminNick, $blacklist)) {
$this->updateStatus('The user nickname "' . htmlspecialchars($this->adminNick) .
'" is reserved.', true);
$fail = true;
}
if (empty($this->adminPass)) {
$this->updateStatus("No initial StatusNet user password specified.", true);
$fail = true;
}
return !$fail;
}
/**
* Set up the database with the appropriate function for the selected type...
* Saves database info into $this->db.
*
* @return mixed array of database connection params on success, false on failure
*/
function setupDatabase()
{
if ($this->db) {
throw new Exception("Bad order of operations: DB already set up.");
}
$method = self::$dbModules[$this->dbtype]['installer'];
$db = call_user_func(array($this, $method),
$this->host,
$this->database,
$this->username,
$this->password);
$this->db = $db;
return $this->db;
}
/**
* Set up a database on PostgreSQL.
* Will output status updates during the operation.
*
* @param string $host
* @param string $database
* @param string $username
* @param string $password
* @return mixed array of database connection params on success, false on failure
*
* @fixme escape things in the connection string in case we have a funny pass etc
*/
function Pgsql_Db_installer($host, $database, $username, $password)
{
$connstring = "dbname=$database host=$host user=$username";
//No password would mean trust authentication used.
if (!empty($password)) {
$connstring .= " password=$password";
}
$this->updateStatus("Starting installation...");
$this->updateStatus("Checking database...");
$conn = pg_connect($connstring);
if ($conn ===false) {
$this->updateStatus("Failed to connect to database: $connstring");
return false;
}
//ensure database encoding is UTF8
$record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
if ($record->server_encoding != 'UTF8') {
$this->updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
return false;
}
$this->updateStatus("Running database script...");
//wrap in transaction;
pg_query($conn, 'BEGIN');
$res = $this->runDbScript('statusnet_pg.sql', $conn, 'pgsql');
if ($res === false) {
$this->updateStatus("Can't run database script.", true);
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
$this->updateStatus(sprintf("Adding %s data to database...", $name));
$res = $this->runDbScript($scr.'.sql', $conn, 'pgsql');
if ($res === false) {
$this->updateStatus(sprintf("Can't run %d script.", $name), true);
return false;
}
}
pg_query($conn, 'COMMIT');
if (empty($password)) {
$sqlUrl = "pgsql://$username@$host/$database";
} else {
$sqlUrl = "pgsql://$username:$password@$host/$database";
}
$db = array('type' => 'pgsql', 'database' => $sqlUrl);
return $db;
}
/**
* Set up a database on MySQL.
* Will output status updates during the operation.
*
* @param string $host
* @param string $database
* @param string $username
* @param string $password
* @return mixed array of database connection params on success, false on failure
*
* @fixme be consistent about using mysqli vs mysql!
* @fixme escape things in the connection string in case we have a funny pass etc
*/
function Mysql_Db_installer($host, $database, $username, $password)
{
$this->updateStatus("Starting installation...");
$this->updateStatus("Checking database...");
$conn = mysql_connect($host, $username, $password);
if (!$conn) {
$this->updateStatus("Can't connect to server '$host' as '$username'.", true);
return false;
}
$this->updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
$this->updateStatus("Can't change to database.", true);
return false;
}
$this->updateStatus("Running database script...");
$res = $this->runDbScript('statusnet.sql', $conn);
if ($res === false) {
$this->updateStatus("Can't run database script.", true);
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
'foreign_services' => 'foreign service')
as $scr => $name) {
$this->updateStatus(sprintf("Adding %s data to database...", $name));
$res = $this->runDbScript($scr.'.sql', $conn);
if ($res === false) {
$this->updateStatus(sprintf("Can't run %d script.", $name), true);
return false;
}
}
$sqlUrl = "mysqli://$username:$password@$host/$database";
$db = array('type' => 'mysql', 'database' => $sqlUrl);
return $db;
}
/**
* Write a stock configuration file.
*
* @return boolean success
*
* @fixme escape variables in output in case we have funny chars, apostrophes etc
*/
function writeConf()
{
// assemble configuration file in a string
$cfg = "<?php\n".
"if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
// site name
"\$config['site']['name'] = '{$this->sitename}';\n\n".
// site location
"\$config['site']['server'] = '{$this->server}';\n".
"\$config['site']['path'] = '{$this->path}'; \n\n".
// checks if fancy URLs are enabled
($this->fancy ? "\$config['site']['fancy'] = true;\n\n":'').
// database
"\$config['db']['database'] = '{$this->db['database']}';\n\n".
($this->db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
"\$config['db']['type'] = '{$this->db['type']}';\n\n";
// write configuration file out to install directory
$res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
return $res;
}
/**
* Install schema into the database
*
* @param string $filename location of database schema file
* @param dbconn $conn connection to database
* @param string $type type of database, currently mysql or pgsql
*
* @return boolean - indicating success or failure
*/
function runDbScript($filename, $conn, $type = 'mysqli')
{
$sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename));
$stmts = explode(';', $sql);
foreach ($stmts as $stmt) {
$stmt = trim($stmt);
if (!mb_strlen($stmt)) {
continue;
}
// FIXME: use PEAR::DB or PDO instead of our own switch
switch ($type) {
case 'mysqli':
$res = mysql_query($stmt, $conn);
if ($res === false) {
$error = mysql_error();
}
break;
case 'pgsql':
$res = pg_query($conn, $stmt);
if ($res === false) {
$error = pg_last_error();
}
break;
default:
$this->updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
}
if ($res === false) {
$this->updateStatus("ERROR ($error) for SQL '$stmt'");
return $res;
}
}
return true;
}
/**
* Create the initial admin user account.
* Side effect: may load portions of StatusNet framework.
* Side effect: outputs program info
*/
function registerInitialUser()
{
define('STATUSNET', true);
define('LACONICA', true); // compatibility
require_once INSTALLDIR . '/lib/common.php';
$data = array('nickname' => $this->adminNick,
'password' => $this->adminPass,
'fullname' => $this->adminNick);
if ($this->adminEmail) {
$data['email'] = $this->adminEmail;
}
$user = User::register($data);
if (empty($user)) {
return false;
}
// give initial user carte blanche
$user->grantRole('owner');
$user->grantRole('moderator');
$user->grantRole('administrator');
// Attempt to do a remote subscribe to update@status.net
// Will fail if instance is on a private network.
if ($this->adminUpdates && class_exists('Ostatus_profile')) {
try {
$oprofile = Ostatus_profile::ensureProfileURL('http://update.status.net/');
Subscription::start($user->getProfile(), $oprofile->localProfile());
$this->updateStatus("Set up subscription to <a href='http://update.status.net/'>update@status.net</a>.");
} catch (Exception $e) {
$this->updateStatus("Could not set up subscription to <a href='http://update.status.net/'>update@status.net</a>.", true);
}
}
return true;
}
/**
* The beef of the installer!
* Create database, config file, and admin user.
*
* Prerequisites: validation of input data.
*
* @return boolean success
*/
function doInstall()
{
$this->db = $this->setupDatabase();
if (!$this->db) {
// database connection failed, do not move on to create config file.
return false;
}
if (!$this->skipConfig) {
$this->updateStatus("Writing config file...");
$res = $this->writeConf();
if (!$res) {
$this->updateStatus("Can't write config file.", true);
return false;
}
}
if (!empty($this->adminNick)) {
// Okay, cross fingers and try to register an initial user
if ($this->registerInitialUser()) {
$this->updateStatus(
"An initial user with the administrator role has been created."
);
} else {
$this->updateStatus(
"Could not create initial StatusNet user (administrator).",
true
);
return false;
}
}
/*
TODO https needs to be considered
*/
$link = "http://".$this->server.'/'.$this->path;
$this->updateStatus("StatusNet has been installed at $link");
$this->updateStatus(
"<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$this->adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>."
);
return true;
}
/**
* Output a pre-install-time warning message
* @param string $message HTML ok, but should be plaintext-able
* @param string $submessage HTML ok, but should be plaintext-able
*/
abstract function warning($message, $submessage='');
/**
* Output an install-time progress message
* @param string $message HTML ok, but should be plaintext-able
* @param boolean $error true if this should be marked as an error condition
*/
abstract function updateStatus($status, $error=false);
}

View File

@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @link http://status.net/ * @link http://status.net/
*/ */
class ProfileFormAction extends Action class ProfileFormAction extends RedirectingAction
{ {
var $profile = null; var $profile = null;
@ -101,29 +101,6 @@ class ProfileFormAction extends Action
} }
} }
/**
* Return to the calling page based on hidden arguments
*
* @return void
*/
function returnToArgs()
{
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} else if (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
}
if ($action) {
common_redirect(common_local_url($action, $args), 303);
} else {
$this->clientError(_("No return-to arguments."));
}
}
/** /**
* handle a POST request * handle a POST request
* *

96
lib/redirectingaction.php Normal file
View File

@ -0,0 +1,96 @@
<?php
/**
* Superclass for actions that redirect to a given return-to page on completion.
*
* PHP version 5
*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009-2010, 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/>.
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Brion Vibber <brion@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Superclass for actions that redirect to a given return-to page on completion.
*
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Brion Vibber <brion@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*/
class RedirectingAction extends Action
{
/**
* Redirect browser to the page our hidden parameters requested,
* or if none given, to the url given by $this->defaultReturnTo().
*
* To be called only after successful processing.
*
* @fixme rename this -- it obscures Action::returnToArgs() which
* returns a list of arguments, and is a bit confusing.
*
* @return void
*/
function returnToArgs()
{
// Now, gotta figure where we go back to
$action = false;
$args = array();
$params = array();
foreach ($this->args as $k => $v) {
if ($k == 'returnto-action') {
$action = $v;
} else if (substr($k, 0, 15) == 'returnto-param-') {
$params[substr($k, 15)] = $v;
} elseif (substr($k, 0, 9) == 'returnto-') {
$args[substr($k, 9)] = $v;
}
}
if ($action) {
common_redirect(common_local_url($action, $args, $params), 303);
} else {
$url = $this->defaultReturnToUrl();
}
common_redirect($url, 303);
}
/**
* If we reached this form without returnto arguments, where should
* we go? May be overridden by subclasses to a reasonable destination
* for that action; default implementation throws an exception.
*
* @return string URL
*/
function defaultReturnTo()
{
$this->clientError(_("No return-to arguments."));
}
}

View File

@ -862,7 +862,14 @@ function common_xml_safe_str($str)
function common_tag_link($tag) function common_tag_link($tag)
{ {
$canonical = common_canonical_tag($tag); $canonical = common_canonical_tag($tag);
if (common_config('singleuser', 'enabled')) {
// regular TagAction isn't set up in 1user mode
$url = common_local_url('showstream',
array('nickname' => common_config('singleuser', 'nickname'),
'tag' => $canonical));
} else {
$url = common_local_url('tag', array('tag' => $canonical)); $url = common_local_url('tag', array('tag' => $canonical));
}
$xs = new XMLStringer(); $xs = new XMLStringer();
$xs->elementStart('span', 'tag'); $xs->elementStart('span', 'tag');
$xs->element('a', array('href' => $url, $xs->element('a', array('href' => $url,
@ -1272,12 +1279,38 @@ function common_mtrand($bytes)
return $enc; return $enc;
} }
/**
* Record the given URL as the return destination for a future
* form submission, to be read by common_get_returnto().
*
* @param string $url
*
* @fixme as a session-global setting, this can allow multiple forms
* to conflict and overwrite each others' returnto destinations if
* the user has multiple tabs or windows open.
*
* Should refactor to index with a token or otherwise only pass the
* data along its intended path.
*/
function common_set_returnto($url) function common_set_returnto($url)
{ {
common_ensure_session(); common_ensure_session();
$_SESSION['returnto'] = $url; $_SESSION['returnto'] = $url;
} }
/**
* Fetch a return-destination URL previously recorded by
* common_set_returnto().
*
* @return mixed URL string or null
*
* @fixme as a session-global setting, this can allow multiple forms
* to conflict and overwrite each others' returnto destinations if
* the user has multiple tabs or windows open.
*
* Should refactor to index with a token or otherwise only pass the
* data along its intended path.
*/
function common_get_returnto() function common_get_returnto()
{ {
common_ensure_session(); common_ensure_session();

View File

@ -458,7 +458,7 @@ class GeonamesPlugin extends Plugin
} }
if (!$result->isOk()) { if (!$result->isOk()) {
throw new Exception("HTTP error code " . $result->code); throw new Exception("HTTP error code " . $result->getStatus());
} }
$body = $result->getBody(); $body = $result->getBody();

View File

@ -234,24 +234,6 @@ class MobileProfilePlugin extends WAP20Plugin
} }
function onStartShowHeadElements($action)
{
// @fixme nothing appears to set a serveMobile on any action,
// so this is useless and spews errors. Is this supposed to be
// checking $this?
//if (!$action->serveMobile) {
// return true;
//}
$action->showTitle();
$action->showShortcutIcon();
$action->showStylesheets();
$action->showFeeds();
$action->showDescription();
$action->extraHead();
}
function onStartShowStatusNetStyles($action) function onStartShowStatusNetStyles($action)
{ {
if (!$this->serveMobile) { if (!$this->serveMobile) {

76
scripts/fixup_blocks.php Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2010 StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$longoptions = array('dry-run', 'start=', 'end=');
$helptext = <<<END_OF_HELP
fixup_blocks.php [options]
Finds profile blocks where the unsubscription didn't complete,
and removes the offending subscriptions.
--dry-run look but don't touch
END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
/**
* Fetch subscriptions that should be disallowed by a block
*/
function get_blocked_subs()
{
$query = "SELECT subscription.* " .
"FROM subscription " .
"INNER JOIN profile_block " .
"ON blocker=subscribed " .
"AND blocked=subscriber";
$subscription = new Subscription();
$subscription->query($query);
return $subscription;
}
$dry = have_option('dry-run');
$sub = get_blocked_subs();
$count = $sub->N;
while ($sub->fetch()) {
$subber = Profile::staticGet('id', $sub->subscriber);
$subbed = Profile::staticGet('id', $sub->subscribed);
if (!$subber || !$subbed) {
print "Bogus entry! $sub->subscriber subbed to $sub->subscribed\n";
continue;
}
print "$subber->nickname ($subber->id) blocked but subbed to $subbed->nickname ($subbed->id)";
if ($dry) {
print ": skipping; dry run\n";
} else {
Subscription::cancel($subber, $subbed);
print ": removed\n";
}
}
print "\n";
if ($dry && $count > 0) {
print "Be sure to run without --dry-run to remove the bad entries!\n";
} else {
print "done.\n";
}

217
scripts/install_cli.php Executable file
View File

@ -0,0 +1,217 @@
#!/usr/bin/env php
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, 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/>.
*
* @category Installation
* @package Installation
*
* @author Brion Vibber <brion@status.net>
* @license GNU Affero General Public License http://www.gnu.org/licenses/
* @version 0.9.x
* @link http://status.net
*/
if (php_sapi_name() !== 'cli') {
exit(1);
}
define('INSTALLDIR', dirname(dirname(__FILE__)));
set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib');
require_once INSTALLDIR . '/lib/installer.php';
require_once 'Console/Getopt.php';
class CliInstaller extends Installer
{
public $verbose = true;
/**
* Go for it!
* @return boolean success
*/
function main()
{
if (!$this->checkPrereqs()) {
return false;
}
if ($this->prepare()) {
return $this->handle();
} else {
$this->showHelp();
return false;
}
}
/**
* Get our input parameters...
* @return boolean success
*/
function prepare()
{
$shortoptions = 'qvh';
$longoptions = array('quiet', 'verbose', 'help', 'skip-config');
$map = array(
'-s' => 'server',
'--server' => 'server',
'-p' => 'path',
'--path' => 'path',
'--sitename' => 'sitename',
'--fancy' => 'fancy',
'--dbtype' => 'dbtype',
'--host' => 'host',
'--database' => 'database',
'--username' => 'username',
'--password' => 'password',
'--admin-nick' => 'adminNick',
'--admin-pass' => 'adminPass',
'--admin-email' => 'adminEmail',
'--admin-updates' => 'adminUpdates'
);
foreach ($map as $arg => $target) {
if (substr($arg, 0, 2) == '--') {
$longoptions[] = substr($arg, 2) . '=';
} else {
$shortoptions .= substr($arg, 1) . ':';
}
}
$parser = new Console_Getopt();
$result = $parser->getopt($_SERVER['argv'], $shortoptions, $longoptions);
if (PEAR::isError($result)) {
$this->warning($result->getMessage());
return false;
}
list($options, $args) = $result;
// defaults
$this->dbtype = 'mysql';
$this->adminUpdates = true;
$this->verbose = true;
foreach ($options as $option) {
$arg = $option[0];
if (isset($map[$arg])) {
$var = $map[$arg];
$this->$var = $option[1];
if ($var == 'adminUpdates' || $arg == '--fancy') {
$this->$var = ($option[1] != 'false') && ($option[1] != 'no');
}
} else if ($arg == '--skip-config') {
$this->skipConfig = true;
} else if ($arg == 'q' || $arg == '--quiet') {
$this->verbose = false;
} else if ($arg == 'v' || $arg == '--verbose') {
$this->verbose = true;
} else if ($arg == 'h' || $arg == '--help') {
// will go back to show help
return false;
}
}
$fail = false;
if (empty($this->server)) {
$this->updateStatus("You must specify a web server for the site.", true);
// path is optional though
$fail = true;
}
if (!$this->validateDb()) {
$fail = true;
}
if (!$this->validateAdmin()) {
$fail = true;
}
return !$fail;
}
function handle()
{
return $this->doInstall();
}
function showHelp()
{
echo <<<END_HELP
install_cli.php - StatusNet command-line installer
-s --server=<name> Use <name> as server name (required)
-p --path=<path> Use <path> as path name
--sitename User-friendly site name (required)
--fancy Whether to use fancy URLs (default no)
--dbtype 'mysql' (default) or 'pgsql'
--host Database hostname (required)
--database Database/schema name (required)
--username Database username (required)
--password Database password (required)
--admin-nick Administrator nickname (required)
--admin-pass Initial password for admin user (required)
--admin-email Initial email address for admin user
--admin-updates 'yes' (default) or 'no', whether to subscribe
admin to update@status.net (default yes)
--skip-config Don't write a config.php -- use with caution,
requires a global configuration file.
General options:
-q --quiet Quiet (little output)
-v --verbose Verbose (lots of output)
-h --help Show this message and quit.
END_HELP;
}
function warning($message, $submessage='')
{
print $this->html2text($message) . "\n";
if ($submessage != '') {
print " " . $this->html2text($submessage) . "\n";
}
print "\n";
}
function updateStatus($status, $error=false)
{
if ($this->verbose || $error) {
if ($error) {
print "ERROR: ";
}
print $this->html2text($status);
print "\n";
}
}
private function html2text($html)
{
// break out any links for text legibility
$breakout = preg_replace('/<a[^>+]\bhref="(.*)"[^>]*>(.*)<\/a>/',
'\2 &lt;\1&gt;',
$html);
return html_entity_decode(strip_tags($breakout));
}
}
$installer = new CliInstaller();
$ok = $installer->main();
exit($ok ? 0 : 1);

View File

@ -33,13 +33,12 @@ END_OF_SETTAG_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc'; require_once INSTALLDIR.'/scripts/commandline.inc';
if (count($args) != 2) { if (count($args) < 1) {
show_help(); show_help();
exit(1); exit(1);
} }
$nickname = $args[0]; $nickname = $args[0];
$tag = strtolower($args[1]);
$sn = Status_network::memGet('nickname', $nickname); $sn = Status_network::memGet('nickname', $nickname);
@ -50,6 +49,12 @@ if (empty($sn)) {
$tags = $sn->getTags(); $tags = $sn->getTags();
if (count($args) == 1) {
print(implode(', ', $tags) . "\n");
exit(0);
}
$tag = $args[1];
$i = array_search($tag, $tags); $i = array_search($tag, $tags);
if ($i !== false) { if ($i !== false) {

116
scripts/strip_geo.php Executable file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env php
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2009-2010, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i::n::y';
$longoptions = array('id=', 'nickname=', 'yes', 'dry-run');
$helptext = <<<END_OF_HELP
strip_geo.php [options]
Removes geolocation info from the given user's notices.
-i --id ID of the user (may be a remote profile)
-n --nickname nickname of the user
-y --yes do not wait for confirmation
--dry-run list affected notices without deleting
END_OF_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
if (have_option('i', 'id')) {
$id = get_option_value('i', 'id');
$profile = Profile::staticGet('id', $id);
if (empty($profile)) {
print "Can't find local or remote profile with ID $id\n";
exit(1);
}
} else if (have_option('n', 'nickname')) {
$nickname = get_option_value('n', 'nickname');
$user = User::staticGet('nickname', $nickname);
if (empty($user)) {
print "Can't find local user with nickname '$nickname'\n";
exit(1);
}
$profile = $user->getProfile();
} else {
print "You must provide either an ID or a nickname.\n\n";
show_help();
exit(1);
}
if (!have_option('y', 'yes') && !have_option('--dry-run')) {
print "About to PERMANENTLY remove geolocation data from user '{$profile->nickname}' ({$profile->id})'s notices. Are you sure? [y/N] ";
$response = fgets(STDIN);
if (strtolower(trim($response)) != 'y') {
print "Aborting.\n";
exit(0);
}
}
// @fixme for a very prolific poster this could be too many.
print "Finding notices with geolocation data...";
$notice = new Notice();
$notice->profile_id = $profile->id;
$notice->whereAdd("lat != ''");
$notice->find();
if ($notice->N) {
print " $notice->N found.\n";
while ($notice->fetch()) {
print "notice id $notice->id ";
if (have_option('v') || have_option('--verbose')) {
print "({$notice->lat},{$notice->lon}) ";
if ($notice->location_ns) {
print "ns {$notice->location_ns} id {$notice->location_id} ";
}
}
if (have_option('--dry-run')) {
// sucka
echo "(skipped)";
} else {
// note: setting fields to null and calling update() doesn't save the nulled fields
$orig = clone($notice);
$update = clone($notice);
// In theory we could hit a chunk of notices at once in the UPDATE,
// but we're going to have to decache them individually anyway and
// it doesn't hurt to make sure we don't hold up replication with
// what might be a very slow single UPDATE.
$query = sprintf('UPDATE notice ' .
'SET lat=NULL,lon=NULL,location_ns=NULL,location_id=NULL ' .
'WHERE id=%d', $notice->id);
$ok = $update->query($query);
if ($ok) {
// And now we decache him manually, as query() doesn't know what we're doing...
$orig->blow();
echo "(removed)";
} else {
echo "(failed?)";
}
}
print "\n";
}
} else {
print " none found.\n";
}
print "DONE.\n";