From f09e81ff5263da2342a41ed24a428b1e0ef7d851 Mon Sep 17 00:00:00 2001 From: Christopher Vollick Date: Wed, 7 Apr 2010 07:17:05 -0400 Subject: [PATCH 01/12] Made it so that settag would list tags. Very useful to be able to know what tags a site already has. --- scripts/settag.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/settag.php b/scripts/settag.php index e91d5eb505..d1b06ff100 100644 --- a/scripts/settag.php +++ b/scripts/settag.php @@ -33,13 +33,12 @@ END_OF_SETTAG_HELP; require_once INSTALLDIR.'/scripts/commandline.inc'; -if (count($args) != 2) { +if (count($args) < 1) { show_help(); exit(1); } $nickname = $args[0]; -$tag = strtolower($args[1]); $sn = Status_network::memGet('nickname', $nickname); @@ -50,6 +49,12 @@ if (empty($sn)) { $tags = $sn->getTags(); +if (count($args) == 1) { + print(implode(', ', $tags) . "\n"); + exit(0); +} +$tag = $args[1]; + $i = array_search($tag, $tags); if ($i !== false) { From aceaeb99e5fa451fcfb2ac444443f17c09a4908c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 7 Apr 2010 11:40:16 -0700 Subject: [PATCH 02/12] fixup_blocks.php: finds any stray subscriptions in violation of blocks, and removes them. --- scripts/fixup_blocks.php | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 scripts/fixup_blocks.php diff --git a/scripts/fixup_blocks.php b/scripts/fixup_blocks.php new file mode 100755 index 0000000000..6b0255e720 --- /dev/null +++ b/scripts/fixup_blocks.php @@ -0,0 +1,76 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$longoptions = array('dry-run', 'start=', 'end='); + +$helptext = <<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"; +} From f255b368edc06db8af57f7ad4d13542a126c42bc Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 7 Apr 2010 17:47:08 -0700 Subject: [PATCH 03/12] Refactor installer into base class plus web and CLI installers. scripts/install_cli.php is a bit experimental at present but works. --- install.php | 1046 ++++++++------------------------------- lib/installer.php | 578 +++++++++++++++++++++ scripts/install_cli.php | 217 ++++++++ 3 files changed, 1002 insertions(+), 839 deletions(-) create mode 100644 lib/installer.php create mode 100755 scripts/install_cli.php diff --git a/install.php b/install.php index 8a299f8975..08555d19b9 100644 --- a/install.php +++ b/install.php @@ -1,8 +1,7 @@ - '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('

Config file "config.php" already exists.

'); - $pass = false; - } - - if (version_compare(PHP_VERSION, '5.2.3', '<')) { - printf('

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) { - printf('

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 our documentation page on fixing this.

'); - $pass = false; - } - - $reqs = array('gd', 'curl', - 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml'); - - foreach ($reqs as $req) { - if (!checkExtension($req)) { - printf('

Cannot load required extension: %s

', $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('

Cannot find mysql or pgsql extension. You need one or the other.'); - $pass = false; - } - - if (!is_writable(INSTALLDIR)) { - printf('

Cannot write config file to: %s

', INSTALLDIR); - printf('

On your server, try this command: chmod a+w %s', 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('

Cannot write to %s directory: %s

', $fileSubdir, $fileFullPath); - printf('

On your server, try this command: chmod a+w %s

', $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<< -

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.

-

On Debian based distributions, such as Ubuntu, use a package manager (such as "aptitude", "apt-get", and "synaptic") to install the package listed.

-

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 "yum", "apt-rpm", and "up2date") to install the package listed.

-

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 "pear install <name>".

- -

Absent Libraries

-
    -E_O_T; - foreach ($absent_libraries as $library) { - echo '
  • '; - if (isset($library['url'])) { - echo ''.htmlentities($library['name']).''; - } else { - echo htmlentities($library['name']); - } - echo '
      '; - if (isset($library['deb'])) { - echo '
    • deb: ' . htmlentities($library['deb']) . '
    • '; - } - if (isset($library['rpm'])) { - echo '
    • rpm: ' . htmlentities($library['rpm']) . '
    • '; - } - if (isset($library['pear'])) { - echo '
    • pear: ' . htmlentities($library['pear']) . '
    • '; - } - echo '
    '; - } - echo<< -

    Installed Libraries

    -
      -E_O_T; - foreach ($present_libraries as $library) { - echo '
    • '; - if (isset($library['url'])) { - echo ''.htmlentities($library['name']).''; - } else { - echo htmlentities($library['name']); - } - echo '
    • '; - } - echo<< -E_O_T; -} +require INSTALLDIR . '/lib/installer.php'; /** * Helper class for building form @@ -462,457 +54,230 @@ class Posted { } } -function showForm() +/** + * Web-based installer: provides a form and such. + */ +class WebInstaller extends Installer { - global $dbModules; - $post = new Posted(); - $dbRadios = ''; - if (isset($_POST['dbtype'])) { - $dbtype = $_POST['dbtype']; - } else { - $dbtype = null; - } - foreach ($dbModules as $type => $info) { - if (checkExtension($info['check_module'])) { - if ($dbtype == null || $dbtype == $type) { - $checked = 'checked="checked" '; - $dbtype = $type; // if we didn't have one checked, hit the first - } else { - $checked = ''; - } - $dbRadios .= " $info[name]
      \n"; + /** + * 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(); } } - echo<< - - -
      -
      -
      - Site settings -
        -
      • - - -

        The name of your site

        -
      • -
      • - - enable
        - disable
        -

        Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.

        -
      • -
      -
      + /** + * Web implementation of warning output + */ + function warning($message, $submessage='') + { + print "

      $message

      \n"; + if ($submessage != '') { + print "

      $submessage

      \n"; + } + } -
      - Database settings -
        -
      • - - -

        Database hostname

        -
      • -
      • - - $dbRadios -

        Database type

        -
      • -
      • - - -

        Database name

        -
      • -
      • - - -

        Database username

        -
      • -
      • - - -

        Database password (optional)

        -
      • -
      -
      + /** + * Web implementation of status output + */ + function updateStatus($status, $error=false) + { + echo '$status"; + } -
      - Administrator settings -
        -
      • - - -

        Nickname for the initial StatusNet user (administrator)

        -
      • -
      • - - -

        Password for the initial StatusNet user (administrator)

        -
      • -
      • - - -
      • -
      • - - -

        Optional email address for the initial StatusNet user (administrator)

        -
      • -
      • - - -

        Release and security feed from update@status.net (recommended)

        -
      • -
      + /** + * Show the web form! + */ + function showForm() + { + global $dbModules; + $post = new Posted(); + $dbRadios = ''; + if (isset($_POST['dbtype'])) { + $dbtype = $_POST['dbtype']; + } else { + $dbtype = null; + } + foreach (self::$dbModules as $type => $info) { + if ($this->checkExtension($info['check_module'])) { + if ($dbtype == null || $dbtype == $type) { + $checked = 'checked="checked" '; + $dbtype = $type; // if we didn't have one checked, hit the first + } else { + $checked = ''; + } + $dbRadios .= " $info[name]
      \n"; + } + } + + echo<< +
      +
      + Site settings +
        +
      • + + +

        The name of your site

        +
      • +
      • + + enable
        + disable
        +

        Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.

        +
      • +
      +
      + +
      + Database settings +
        +
      • + + +

        Database hostname

        +
      • +
      • + + $dbRadios +

        Database type

        +
      • +
      • + + +

        Database name

        +
      • +
      • + + +

        Database username

        +
      • +
      • + + +

        Database password (optional)

        +
      • +
      +
      + +
      + Administrator settings +
        +
      • + + +

        Nickname for the initial StatusNet user (administrator)

        +
      • +
      • + + +

        Password for the initial StatusNet user (administrator)

        +
      • +
      • + + +
      • +
      • + + +

        Optional email address for the initial StatusNet user (administrator)

        +
      • +
      • + + +

        Release and security feed from update@status.net (recommended)

        +
      • +
      +
      +
      - -
      - + E_O_T; -} + } -function updateStatus($status, $error=false) -{ - echo '$status"; -} - -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 << -
      Page notice
      -
      -
        + /** + * Handle a POST submission... if we have valid input, start the install! + * Otherwise shows the form along with any error messages. + */ + function handlePost() + { + echo << +
        Page notice
        +
        +
          STR; - $fail = false; - - if (empty($host)) { - updateStatus("No hostname specified.", true); - $fail = true; - } - - if (empty($database)) { - updateStatus("No database specified.", true); - $fail = true; - } - - 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( - "DONE! You can visit your new StatusNet site (login as '$adminNick'). If this is your first StatusNet install, you may want to poke around our Getting Started guide." - ); -} - -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; + $this->validated = $this->prepare(); + if ($this->validated) { + $this->doInstall(); } - } - 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; + echo << +
        + +STR; + if (!$this->validated) { + $this->showForm(); } } - $sqlUrl = "mysqli://$username:$password@$host/$database"; - $db = array('type' => 'mysql', 'database' => $sqlUrl); - return $db; -} + /** + * Read and validate input data. + * May output side effects. + * + * @return boolean success + */ + function prepare() + { + $this->host = $_POST['host']; + $this->dbtype = $_POST['dbtype']; + $this->database = $_POST['database']; + $this->username = $_POST['dbusername']; + $this->password = $_POST['dbpassword']; + $this->sitename = $_POST['sitename']; + $this->fancy = !empty($_POST['fancy']); -function writeConf($sitename, $server, $path, $fancy, $db) -{ - // assemble configuration file in a string - $cfg = "adminNick = strtolower($_POST['admin_nickname']); + $this->adminPass = $_POST['admin_password']; + $adminPass2 = $_POST['admin_password2']; + $this->adminEmail = $_POST['admin_email']; + $this->adminUpdates = $_POST['admin_updates']; - // site name - "\$config['site']['name'] = '$sitename';\n\n". + $this->server = $_SERVER['HTTP_HOST']; + $this->path = substr(dirname($_SERVER['PHP_SELF']), 1); - // 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 - * - * @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($filename)); - $stmts = explode(';', $sql); - foreach ($stmts as $stmt) { - $stmt = trim($stmt); - if (!mb_strlen($stmt)) { - continue; + $fail = false; + if (!$this->validateDb()) { + $fail = true; } - // 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: - updateStatus("runDbScript() error: unknown database type ". $type ." provided."); + + if (!$this->validateAdmin()) { + $fail = true; } - if ($res === false) { - updateStatus("ERROR ($error) for SQL '$stmt'"); - return $res; + + if ($this->adminPass != $adminPass2) { + $this->updateStatus("Administrator passwords do not match. Did you mistype?", true); + $fail = true; } - } - return true; -} - -function registerInitialUser($nickname, $password, $email, $adminUpdates) -{ - 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; + + return !$fail; } - // 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 (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 update@status.net."); - } catch (Exception $e) { - updateStatus("Could not set up subscription to update@status.net."); - } - } - - return true; } ?> @@ -945,7 +310,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

        Install StatusNet

        - +main(); +?>
        diff --git a/lib/installer.php b/lib/installer.php new file mode 100644 index 0000000000..d0e46f95c8 --- /dev/null +++ b/lib/installer.php @@ -0,0 +1,578 @@ +. + * + * @category Installation + * @package Installation + * + * @author Adrian Lang + * @author Brenda Wallace + * @author Brett Taylor + * @author Brion Vibber + * @author CiaranG + * @author Craig Andrews + * @author Eric Helgeson + * @author Evan Prodromou + * @author Robin Millette + * @author Sarven Capadisli + * @author Tom Adams + * @author Zach Copley + * @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 our documentation page 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: %s', $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: %s

        ', INSTALLDIR), + sprintf('On your server, try this command: chmod a+w %s', 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: %s', $fileSubdir, $fileFullPath), + sprintf('On your server, try this command: chmod a+w %s', $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 = "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 update@status.net."); + } catch (Exception $e) { + $this->updateStatus("Could not set up subscription to update@status.net.", 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( + "DONE! You can visit your new StatusNet site (login as '$this->adminNick'). If this is your first StatusNet install, you may want to poke around our Getting Started guide." + ); + + 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); + +} diff --git a/scripts/install_cli.php b/scripts/install_cli.php new file mode 100755 index 0000000000..61fbe18ef6 --- /dev/null +++ b/scripts/install_cli.php @@ -0,0 +1,217 @@ +#!/usr/bin/env php +. + * + * @category Installation + * @package Installation + * + * @author Brion Vibber + * @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 << Use as server name (required) + -p --path= Use 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('/+]\bhref="(.*)"[^>]*>(.*)<\/a>/', + '\2 <\1>', + $html); + return html_entity_decode(strip_tags($breakout)); + } +} + +$installer = new CliInstaller(); +$ok = $installer->main(); +exit($ok ? 0 : 1); From 8a02cad42468c628b7b77910458c706c5d26f4df Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 10:09:56 -0700 Subject: [PATCH 04/12] drop onStartShowHeadElements handler from MobileProfile; just duplicated the original code path, and after removing the bogus notice-spewing code it was running those things twice. --- plugins/MobileProfile/MobileProfilePlugin.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php index 60bb3b68fa..1c61b4fe52 100644 --- a/plugins/MobileProfile/MobileProfilePlugin.php +++ b/plugins/MobileProfile/MobileProfilePlugin.php @@ -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) { if (!$this->serveMobile) { From ecd31384ed9560aac350085733af4039cf344c8a Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 10:11:52 -0700 Subject: [PATCH 05/12] Fix for error during handling of HTTP error response case in Geonames lookups --- plugins/GeonamesPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index 718af9e0b5..bc5899943b 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -458,7 +458,7 @@ class GeonamesPlugin extends Plugin } if (!$result->isOk()) { - throw new Exception("HTTP error code " . $result->code); + throw new Exception("HTTP error code " . $result->getStatus()); } $body = $result->getBody(); From 88678eadfa2e93f540195bd934833e58f23639f7 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 13:12:14 -0700 Subject: [PATCH 06/12] In single-user mode, link #hashtags to the user's tagged stream rather than the global tag action, which isn't registered. Previously they would end up pointing to the home URL. --- lib/util.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/util.php b/lib/util.php index f4ee26bbf0..bbc3341769 100644 --- a/lib/util.php +++ b/lib/util.php @@ -862,7 +862,14 @@ function common_xml_safe_str($str) function common_tag_link($tag) { $canonical = common_canonical_tag($tag); - $url = common_local_url('tag', array('tag' => $canonical)); + 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)); + } $xs = new XMLStringer(); $xs->elementStart('span', 'tag'); $xs->element('a', array('href' => $url, From ab20e75ff8feab01b4fec81c02b8b4039d65cca0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 16:58:54 -0700 Subject: [PATCH 07/12] scripts/strip_geo.php to remove geodata from notices by a given user/profile. May be slow or run out of memory if run on particularly prolific posters -- not yet optimized for that case. Note that geodata that has already been sent out to other services (via ostatus, omb, twitter, etc) will not be removed from them. --- lib/util.php | 9 +--- scripts/strip_geo.php | 116 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 8 deletions(-) create mode 100755 scripts/strip_geo.php diff --git a/lib/util.php b/lib/util.php index bbc3341769..f4ee26bbf0 100644 --- a/lib/util.php +++ b/lib/util.php @@ -862,14 +862,7 @@ function common_xml_safe_str($str) function common_tag_link($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->elementStart('span', 'tag'); $xs->element('a', array('href' => $url, diff --git a/scripts/strip_geo.php b/scripts/strip_geo.php new file mode 100755 index 0000000000..010fb31f54 --- /dev/null +++ b/scripts/strip_geo.php @@ -0,0 +1,116 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::n::y'; +$longoptions = array('id=', 'nickname=', 'yes', 'dry-run'); + +$helptext = <<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"; From 0e0927985cbfbb21c8b60e3c7b0b0a5e2069d3c7 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 17:04:10 -0700 Subject: [PATCH 08/12] Revert "scripts/strip_geo.php to remove geodata from notices by a given user/profile." This reverts commit ab20e75ff8feab01b4fec81c02b8b4039d65cca0. Accidentally removed another commit; clearing up... --- lib/util.php | 9 +++- scripts/strip_geo.php | 116 ------------------------------------------ 2 files changed, 8 insertions(+), 117 deletions(-) delete mode 100755 scripts/strip_geo.php diff --git a/lib/util.php b/lib/util.php index f4ee26bbf0..bbc3341769 100644 --- a/lib/util.php +++ b/lib/util.php @@ -862,7 +862,14 @@ function common_xml_safe_str($str) function common_tag_link($tag) { $canonical = common_canonical_tag($tag); - $url = common_local_url('tag', array('tag' => $canonical)); + 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)); + } $xs = new XMLStringer(); $xs->elementStart('span', 'tag'); $xs->element('a', array('href' => $url, diff --git a/scripts/strip_geo.php b/scripts/strip_geo.php deleted file mode 100755 index 010fb31f54..0000000000 --- a/scripts/strip_geo.php +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::n::y'; -$longoptions = array('id=', 'nickname=', 'yes', 'dry-run'); - -$helptext = <<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"; From 4b2dfabff886c7f2e61eef43c7c9de092661d73a Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 17:05:02 -0700 Subject: [PATCH 09/12] scripts/strip_geo.php to remove geodata from notices by a given user/profile. May be slow or run out of memory if run on particularly prolific posters -- not yet optimized for that case. Note that geodata that has already been sent out to other services (via ostatus, omb, twitter, etc) will not be removed from them. (fixed version -- previous had accidentally undone another commit) --- scripts/strip_geo.php | 116 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100755 scripts/strip_geo.php diff --git a/scripts/strip_geo.php b/scripts/strip_geo.php new file mode 100755 index 0000000000..010fb31f54 --- /dev/null +++ b/scripts/strip_geo.php @@ -0,0 +1,116 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::n::y'; +$longoptions = array('id=', 'nickname=', 'yes', 'dry-run'); + +$helptext = <<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"; From 5dbaaed4e68ecae1c78b9493add89df3557c8e98 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 8 Apr 2010 19:06:55 -0700 Subject: [PATCH 10/12] Maintain 'page' parameter for block from subscribers list, block & make-admin from group members list. Refactored some of the returnto handling code. It looks like we have several different ways of handling this in the software, icky! Marked the session-based functions with fixmes (they'll stomp on other forms when multiple tabs/windows are used) and combined some commonish bits of code between ProfileFormAction and the group block & makeadmin actions where they're using hidden form parameters. Extended that to allow passing dynamic parameters (eg 'page') as well as static ones (action, target user/group). --- actions/groupblock.php | 35 ++++++-------- actions/groupmembers.php | 23 ++++++++-- actions/makeadmin.php | 31 ++++++------- actions/subscribers.php | 10 ++-- lib/profileformaction.php | 25 +--------- lib/redirectingaction.php | 96 +++++++++++++++++++++++++++++++++++++++ lib/util.php | 26 +++++++++++ 7 files changed, 179 insertions(+), 67 deletions(-) create mode 100644 lib/redirectingaction.php diff --git a/actions/groupblock.php b/actions/groupblock.php index 88d7634a28..fc95c0e669 100644 --- a/actions/groupblock.php +++ b/actions/groupblock.php @@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class GroupblockAction extends Action +class GroupblockAction extends RedirectingAction { var $profile = null; var $group = null; @@ -117,9 +117,7 @@ class GroupblockAction extends Action parent::handle($args); if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($this->arg('no')) { - common_redirect(common_local_url('groupmembers', - array('nickname' => $this->group->nickname)), - 303); + $this->returnToArgs(); } elseif ($this->arg('yes')) { $this->blockProfile(); } elseif ($this->arg('blockto')) { @@ -196,23 +194,20 @@ class GroupblockAction extends Action $this->serverError(_("Database error blocking user from group.")); return false; } + + $this->returnToArgs(); + } - // Now, gotta figure where we go back to - 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); - } else { - common_redirect(common_local_url('groupmembers', - array('nickname' => $this->group->nickname)), - 303); - } + /** + * If we reached this form without returnto arguments, default to + * the top of the group's member list. + * + * @return string URL + */ + function defaultReturnTo() + { + return common_local_url('groupmembers', + array('nickname' => $this->group->nickname)); } function showScripts() diff --git a/actions/groupmembers.php b/actions/groupmembers.php index fb4e46dbc0..6d0701239f 100644 --- a/actions/groupmembers.php +++ b/actions/groupmembers.php @@ -205,8 +205,7 @@ class GroupMemberListItem extends ProfileListItem !$this->profile->isAdmin($this->group)) { $this->out->elementStart('li', 'entity_make_admin'); $maf = new MakeAdminForm($this->out, $this->profile, $this->group, - array('action' => 'groupmembers', - 'nickname' => $this->group->nickname)); + $this->returnToArgs()); $maf->show(); $this->out->elementEnd('li'); } @@ -220,8 +219,7 @@ class GroupMemberListItem extends ProfileListItem if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) { $this->out->elementStart('li', 'entity_block'); $bf = new GroupBlockForm($this->out, $this->profile, $this->group, - array('action' => 'groupmembers', - 'nickname' => $this->group->nickname)); + $this->returnToArgs()); $bf->show(); $this->out->elementEnd('li'); } @@ -244,6 +242,23 @@ class GroupMemberListItem extends ProfileListItem $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; + } } /** diff --git a/actions/makeadmin.php b/actions/makeadmin.php index f19348648d..9ccb442308 100644 --- a/actions/makeadmin.php +++ b/actions/makeadmin.php @@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class MakeadminAction extends Action +class MakeadminAction extends RedirectingAction { var $profile = null; var $group = null; @@ -148,20 +148,19 @@ class MakeadminAction extends Action $this->group->getBestName()); } - 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 { - common_redirect(common_local_url('groupmembers', - array('nickname' => $this->group->nickname)), - 303); - } + $this->returnToArgs(); } + + /** + * If we reached this form without returnto arguments, default to + * the top of the group's member list. + * + * @return string URL + */ + function defaultReturnTo() + { + return common_local_url('groupmembers', + array('nickname' => $this->group->nickname)); + } + } diff --git a/actions/subscribers.php b/actions/subscribers.php index 6dda7312d6..6fdf43e2cc 100644 --- a/actions/subscribers.php +++ b/actions/subscribers.php @@ -157,9 +157,13 @@ class SubscribersListItem extends SubscriptionListItem $user = common_current_user(); if (!empty($user) && $this->owner->id == $user->id) { - $bf = new BlockForm($this->out, $this->profile, - array('action' => 'subscribers', - 'nickname' => $this->owner->nickname)); + $returnto = array('action' => 'subscribers', + '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(); } } diff --git a/lib/profileformaction.php b/lib/profileformaction.php index 8a934666e3..0ffafe5fb8 100644 --- a/lib/profileformaction.php +++ b/lib/profileformaction.php @@ -41,7 +41,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @link http://status.net/ */ -class ProfileFormAction extends Action +class ProfileFormAction extends RedirectingAction { 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 * diff --git a/lib/redirectingaction.php b/lib/redirectingaction.php new file mode 100644 index 0000000000..f115852742 --- /dev/null +++ b/lib/redirectingaction.php @@ -0,0 +1,96 @@ +. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @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 + * @author Brion Vibber + * @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.")); + } +} diff --git a/lib/util.php b/lib/util.php index bbc3341769..6905df839a 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1279,12 +1279,38 @@ function common_mtrand($bytes) 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) { common_ensure_session(); $_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() { common_ensure_session(); From eefde36fcf5c24c55fb8906374bac5c4c370cb54 Mon Sep 17 00:00:00 2001 From: Christopher Vollick Date: Fri, 9 Apr 2010 06:03:53 -0400 Subject: [PATCH 11/12] Undefined Variable in foafgroup.php Probably just left over from the past. --- actions/foafgroup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/foafgroup.php b/actions/foafgroup.php index d685554ac4..07cca31811 100644 --- a/actions/foafgroup.php +++ b/actions/foafgroup.php @@ -56,7 +56,7 @@ class FoafGroupAction extends Action return false; } - $local = Local_group::staticGet('nickname', $nickname); + $local = Local_group::staticGet('nickname', $this->nickname); if (!$local) { $this->clientError(_('No such group.'), 404); From 2be04e2a631ea9f373386bed792e7233418e2ee2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 9 Apr 2010 08:36:13 -0700 Subject: [PATCH 12/12] Avoid E_NOTICE spew when listing group members who aren't admins --- actions/foafgroup.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/foafgroup.php b/actions/foafgroup.php index 07cca31811..4db40c28be 100644 --- a/actions/foafgroup.php +++ b/actions/foafgroup.php @@ -126,7 +126,8 @@ class FoafGroupAction extends Action while ($members->fetch()) { $member_uri = common_local_url('userbyid', array('id'=>$members->id)); $member_details[$member_uri] = array( - 'nickname' => $members->nickname + 'nickname' => $members->nickname, + 'is_admin' => false, ); $this->element('member', array('rdf:resource' => $member_uri)); }