4 * StatusNet - the distributed open-source microblogging tool
5 * Copyright (C) 2009, StatusNet, Inc.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * @category Installation
21 * @package Installation
23 * @author Adrian Lang <mail@adrianlang.de>
24 * @author Brenda Wallace <shiny@cpan.org>
25 * @author Brett Taylor <brett@webfroot.co.nz>
26 * @author Brion Vibber <brion@pobox.com>
27 * @author CiaranG <ciaran@ciarang.com>
28 * @author Craig Andrews <candrews@integralblue.com>
29 * @author Eric Helgeson <helfire@Erics-MBP.local>
30 * @author Evan Prodromou <evan@status.net>
31 * @author Robin Millette <millette@controlyourself.ca>
32 * @author Sarven Capadisli <csarven@status.net>
33 * @author Tom Adams <tom@holizz.com>
34 * @author Zach Copley <zach@status.net>
35 * @license GNU Affero General Public License http://www.gnu.org/licenses/
37 * @link http://status.net
40 define('INSTALLDIR', dirname(__FILE__));
42 $external_libraries=array(
45 'url'=>'http://us.php.net/manual/en/book.gettext.php',
46 'check_function'=>'gettext'
50 'url'=>'http://pear.php.net/',
52 'include'=>'PEAR.php',
58 'url'=>'http://pear.php.net/package/DB',
60 'include'=>'DB/common.php',
61 'check_class'=>'DB_common'
64 'name'=>'DB_DataObject',
65 'pear'=>'DB_DataObject',
66 'url'=>'http://pear.php.net/package/DB_DataObject',
67 'include'=>'DB/DataObject.php',
68 'check_class'=>'DB_DataObject'
71 'name'=>'Console_Getopt',
72 'pear'=>'Console_Getopt',
73 'url'=>'http://pear.php.net/package/Console_Getopt',
74 'include'=>'Console/Getopt.php',
75 'check_class'=>'Console_Getopt'
78 'name'=>'Facebook API',
79 'url'=>'http://developers.facebook.com/',
80 'include'=>'facebook/facebook.php',
81 'check_class'=>'Facebook'
85 'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
86 'include'=>'htmLawed/htmLawed.php',
87 'check_function'=>'htmLawed'
90 'name'=>'HTTP_Request',
91 'pear'=>'HTTP_Request',
92 'url'=>'http://pear.php.net/package/HTTP_Request',
93 'deb'=>'php-http-request',
94 'include'=>'HTTP/Request.php',
95 'check_class'=>'HTTP_Request'
98 'name'=>'HTTP_Request2',
99 'pear'=>'HTTP_Request2',
100 'url'=>'http://pear.php.net/package/HTTP_Request2',
101 'include'=>'HTTP/Request2.php',
102 'check_class'=>'HTTP_Request2'
107 'url'=>'http://pear.php.net/package/Mail',
109 'include'=>'Mail.php',
110 'check_class'=>'Mail'
113 'name'=>'Mail_mimeDecode',
114 'pear'=>'Mail_mimeDecode',
115 'url'=>'http://pear.php.net/package/Mail_mimeDecode',
116 'deb'=>'php-mail-mimedecode',
117 'include'=>'Mail/mimeDecode.php',
118 'check_class'=>'Mail_mimeDecode'
123 'url'=>'http://pear.php.net/package/Mime_Type',
124 'include'=>'MIME/Type.php',
125 'check_class'=>'Mime_Type'
128 'name'=>'Net_URL_Mapper',
129 'pear'=>'Net_URL_Mapper',
130 'url'=>'http://pear.php.net/package/Net_URL_Mapper',
131 'include'=>'Net/URL/Mapper.php',
132 'check_class'=>'Net_URL_Mapper'
137 'url'=>'http://pear.php.net/package/Net_LDAP2',
138 'deb'=>'php-net-ldap2',
139 'include'=>'Net/LDAP2.php',
140 'check_class'=>'Net_LDAP2'
143 'name'=>'Net_Socket',
144 'pear'=>'Net_Socket',
145 'url'=>'http://pear.php.net/package/Net_Socket',
146 'deb'=>'php-net-socket',
147 'include'=>'Net/Socket.php',
148 'check_class'=>'Net_Socket'
153 'url'=>'http://pear.php.net/package/Net_SMTP',
154 'deb'=>'php-net-smtp',
155 'include'=>'Net/SMTP.php',
156 'check_class'=>'Net_SMTP'
161 'url'=>'http://pear.php.net/package/Net_URL',
162 'deb'=>'php-net-url',
163 'include'=>'Net/URL.php',
164 'check_class'=>'Net_URL'
169 'url'=>'http://pear.php.net/package/Net_URL2',
170 'include'=>'Net/URL2.php',
171 'check_class'=>'Net_URL2'
174 'name'=>'Services_oEmbed',
175 'pear'=>'Services_oEmbed',
176 'url'=>'http://pear.php.net/package/Services_oEmbed',
177 'include'=>'Services/oEmbed.php',
178 'check_class'=>'Services_oEmbed'
182 'url'=>'http://stomp.codehaus.org/PHP',
183 'include'=>'Stomp.php',
184 'check_class'=>'Stomp'
187 'name'=>'System_Command',
188 'pear'=>'System_Command',
189 'url'=>'http://pear.php.net/package/System_Command',
190 'include'=>'System/Command.php',
191 'check_class'=>'System_Command'
195 'url'=>'http://code.google.com/p/xmpphp',
196 'include'=>'XMPPHP/XMPP.php',
197 'check_class'=>'XMPPHP_XMPP'
200 'name'=>'PHP Markdown',
201 'url'=>'http://www.michelf.com/projects/php-markdown/',
202 'include'=>'markdown.php',
203 'check_class'=>'Markdown_Parser'
207 'url'=>'http://code.google.com/p/oauth-php',
208 'include'=>'OAuth.php',
209 'check_class'=>'OAuthRequest'
214 'url'=>'http://pear.php.net/package/Validate',
215 'include'=>'Validate.php',
216 'check_class'=>'Validate'
222 'check_module' => 'mysql', // mysqli?
223 'installer' => 'mysql_db_installer',
226 'name' => 'PostgreSQL',
227 'check_module' => 'pgsql',
228 'installer' => 'pgsql_db_installer',
233 * the actual installation.
234 * If call libraries are present, then install
240 if (!checkPrereqs()) {
244 if (!empty($_GET['checklibs'])) {
247 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
256 * checks if an external libary is present
258 * @param string $external_library Name of library
260 * @return boolean indicates if library present
262 function haveExternalLibrary($external_library)
264 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
267 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
270 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
276 // Attempt to include a PHP file and report if it worked, while
277 // suppressing the annoying warning messages on failure.
278 function haveIncludeFile($filename) {
279 $old = error_reporting(error_reporting() & ~E_WARNING);
280 $ok = include_once($filename);
281 error_reporting($old);
286 * Check if all is ready for installation
290 function checkPrereqs()
294 if (file_exists(INSTALLDIR.'/config.php')) {
295 printf('<p class="error">Config file "config.php" already exists.</p>');
299 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
300 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
304 $reqs = array('gd', 'curl',
305 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
307 foreach ($reqs as $req) {
308 if (!checkExtension($req)) {
309 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
313 // Make sure we have at least one database module available
315 $missingExtensions = array();
316 foreach ($dbModules as $type => $info) {
317 if (!checkExtension($info['check_module'])) {
318 $missingExtensions[] = $info['check_module'];
322 if (count($missingExtensions) == count($dbModules)) {
323 $req = implode(', ', $missingExtensions);
324 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
328 if (!is_writable(INSTALLDIR)) {
329 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
330 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
334 // Check the subdirs used for file uploads
335 $fileSubdirs = array('avatar', 'background', 'file');
336 foreach ($fileSubdirs as $fileSubdir) {
337 $fileFullPath = INSTALLDIR."/$fileSubdir/";
338 if (!is_writable($fileFullPath)) {
339 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
340 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
349 * Checks if a php extension is both installed and loaded
351 * @param string $name of extension to check
353 * @return boolean whether extension is installed and loaded
355 function checkExtension($name)
357 if (extension_loaded($name)) {
359 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
360 // dl will throw a fatal error if it's disabled or we're in safe mode.
361 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
362 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
363 if (PHP_SHLIB_SUFFIX == 'dll') {
364 $soname = "php_" . $soname;
373 * Show list of libraries
379 global $external_libraries;
380 $present_libraries=array();
381 $absent_libraries=array();
382 foreach ($external_libraries as $external_library) {
383 if (haveExternalLibrary($external_library)) {
384 $present_libraries[]=$external_library;
386 $absent_libraries[]=$external_library;
390 <div class="instructions">
391 <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
392 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
393 <p>On Debian based distributions, such as Ubuntu, use a package manager (such as "aptitude", "apt-get", and "synaptic") to install the package listed.</p>
394 <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 "yum", "apt-rpm", and "up2date") to install the package listed.</p>
395 <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 "pear install <name>".</p>
397 <h2>Absent Libraries</h2>
398 <ul id="absent_libraries">
400 foreach ($absent_libraries as $library) {
402 if (isset($library['url'])) {
403 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
405 echo htmlentities($library['name']);
408 if (isset($library['deb'])) {
409 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
411 if (isset($library['rpm'])) {
412 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
414 if (isset($library['pear'])) {
415 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
421 <h2>Installed Libraries</h2>
422 <ul id="present_libraries">
424 foreach ($present_libraries as $library) {
426 if (isset($library['url'])) {
427 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
429 echo htmlentities($library['name']);
439 * Helper class for building form
442 function value($name)
444 if (isset($_POST[$name])) {
445 return htmlspecialchars(strval($_POST[$name]));
455 $post = new Posted();
457 if (isset($_POST['dbtype'])) {
458 $dbtype = $_POST['dbtype'];
462 foreach ($dbModules as $type => $info) {
463 if (checkExtension($info['check_module'])) {
464 if ($dbtype == null || $dbtype == $type) {
465 $checked = 'checked="checked" ';
466 $dbtype = $type; // if we didn't have one checked, hit the first
470 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
477 <dl id="page_notice" class="system_notice">
480 <div class="instructions">
481 <p>Enter your database connection information below to initialize the database.</p>
485 <form method="post" action="install.php" class="form_settings" id="form_install">
487 <legend>Connection settings</legend>
488 <ul class="form_data">
490 <label for="sitename">Site name</label>
491 <input type="text" id="sitename" name="sitename" value="{$post->value('sitename')}" />
492 <p class="form_guide">The name of your site</p>
495 <label for="fancy-enable">Fancy URLs</label>
496 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
497 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
498 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
501 <label for="host">Hostname</label>
502 <input type="text" id="host" name="host" value="{$post->value('host')}" />
503 <p class="form_guide">Database hostname</p>
507 <label for="dbtype">Type</label>
509 <p class="form_guide">Database type</p>
513 <label for="database">Name</label>
514 <input type="text" id="database" name="database" value="{$post->value('database')}" />
515 <p class="form_guide">Database name</p>
518 <label for="dbusername">DB username</label>
519 <input type="text" id="dbusername" name="dbusername" value="{$post->value('dbusername')}" />
520 <p class="form_guide">Database username</p>
523 <label for="dbpassword">DB password</label>
524 <input type="password" id="dbpassword" name="dbpassword" value="{$post->value('dbpassword')}" />
525 <p class="form_guide">Database password (optional)</p>
528 <label for="admin_nickname">Administrator nickname</label>
529 <input type="text" id="admin_nickname" name="admin_nickname" value="{$post->value('admin_nickname')}" />
530 <p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
533 <label for="admin_password">Administrator password</label>
534 <input type="password" id="admin_password" name="admin_password" value="{$post->value('admin_password')}" />
535 <p class="form_guide">Password for the initial StatusNet user (administrator)</p>
538 <label for="admin_password2">Confirm password</label>
539 <input type="password" id="admin_password2" name="admin_password2" value="{$post->value('admin_password2')}" />
542 <label for="admin_email">Administrator e-mail</label>
543 <input id="admin_email" name="admin_email" value="{$post->value('admin_email')}" />
544 <p class="form_guide">Optional email address for the initial StatusNet user (administrator)</p>
547 <input type="submit" name="submit" class="submit" value="Submit" />
554 function updateStatus($status, $error=false)
556 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
559 function handlePost()
561 $host = $_POST['host'];
562 $dbtype = $_POST['dbtype'];
563 $database = $_POST['database'];
564 $username = $_POST['dbusername'];
565 $password = $_POST['dbpassword'];
566 $sitename = $_POST['sitename'];
567 $fancy = !empty($_POST['fancy']);
569 $adminNick = $_POST['admin_nickname'];
570 $adminPass = $_POST['admin_password'];
571 $adminPass2 = $_POST['admin_password2'];
572 $adminEmail = $_POST['admin_email'];
574 $server = $_SERVER['HTTP_HOST'];
575 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
578 <dl class="system_notice">
586 updateStatus("No hostname specified.", true);
590 if (empty($database)) {
591 updateStatus("No database specified.", true);
595 if (empty($username)) {
596 updateStatus("No username specified.", true);
600 if (empty($sitename)) {
601 updateStatus("No sitename specified.", true);
605 if (empty($adminNick)) {
606 updateStatus("No initial StatusNet user nickname specified.", true);
610 if (empty($adminPass)) {
611 updateStatus("No initial StatusNet user password specified.", true);
615 if ($adminPass != $adminPass2) {
616 updateStatus("Administrator passwords do not match. Did you mistype?", true);
626 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
629 // database connection failed, do not move on to create config file.
633 updateStatus("Writing config file...");
634 $res = writeConf($sitename, $server, $path, $fancy, $db);
637 updateStatus("Can't write config file.", true);
642 // Okay, cross fingers and try to register an initial user
643 if (registerInitialUser($adminNick, $adminPass, $adminEmail)) {
645 "An initial user with the administrator role has been created."
649 "Could not create initial StatusNet user (administrator).",
657 TODO https needs to be considered
659 $link = "http://".$server.'/'.$path;
661 updateStatus("StatusNet has been installed at $link");
663 "You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick')."
667 function Pgsql_Db_installer($host, $database, $username, $password)
669 $connstring = "dbname=$database host=$host user=$username";
671 //No password would mean trust authentication used.
672 if (!empty($password)) {
673 $connstring .= " password=$password";
675 updateStatus("Starting installation...");
676 updateStatus("Checking database...");
677 $conn = pg_connect($connstring);
679 if ($conn ===false) {
680 updateStatus("Failed to connect to database: $connstring");
685 //ensure database encoding is UTF8
686 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
687 if ($record->server_encoding != 'UTF8') {
688 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
693 updateStatus("Running database script...");
694 //wrap in transaction;
695 pg_query($conn, 'BEGIN');
696 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
698 if ($res === false) {
699 updateStatus("Can't run database script.", true);
703 foreach (array('sms_carrier' => 'SMS carrier',
704 'notice_source' => 'notice source',
705 'foreign_services' => 'foreign service')
707 updateStatus(sprintf("Adding %s data to database...", $name));
708 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
709 if ($res === false) {
710 updateStatus(sprintf("Can't run %d script.", $name), true);
715 pg_query($conn, 'COMMIT');
717 if (empty($password)) {
718 $sqlUrl = "pgsql://$username@$host/$database";
720 $sqlUrl = "pgsql://$username:$password@$host/$database";
723 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
728 function Mysql_Db_installer($host, $database, $username, $password)
730 updateStatus("Starting installation...");
731 updateStatus("Checking database...");
733 $conn = mysql_connect($host, $username, $password);
735 updateStatus("Can't connect to server '$host' as '$username'.", true);
739 updateStatus("Changing to database...");
740 $res = mysql_select_db($database, $conn);
742 updateStatus("Can't change to database.", true);
746 updateStatus("Running database script...");
747 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
748 if ($res === false) {
749 updateStatus("Can't run database script.", true);
753 foreach (array('sms_carrier' => 'SMS carrier',
754 'notice_source' => 'notice source',
755 'foreign_services' => 'foreign service')
757 updateStatus(sprintf("Adding %s data to database...", $name));
758 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
759 if ($res === false) {
760 updateStatus(sprintf("Can't run %d script.", $name), true);
766 $sqlUrl = "mysqli://$username:$password@$host/$database";
767 $db = array('type' => 'mysql', 'database' => $sqlUrl);
771 function writeConf($sitename, $server, $path, $fancy, $db)
773 // assemble configuration file in a string
775 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
778 "\$config['site']['name'] = '$sitename';\n\n".
781 "\$config['site']['server'] = '$server';\n".
782 "\$config['site']['path'] = '$path'; \n\n".
784 // checks if fancy URLs are enabled
785 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
788 "\$config['db']['database'] = '{$db['database']}';\n\n".
789 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
790 "\$config['db']['type'] = '{$db['type']}';\n\n";
791 // write configuration file out to install directory
792 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
798 * Install schema into the database
800 * @param string $filename location of database schema file
801 * @param dbconn $conn connection to database
802 * @param string $type type of database, currently mysql or pgsql
804 * @return boolean - indicating success or failure
806 function runDbScript($filename, $conn, $type = 'mysqli')
808 $sql = trim(file_get_contents($filename));
809 $stmts = explode(';', $sql);
810 foreach ($stmts as $stmt) {
812 if (!mb_strlen($stmt)) {
815 // FIXME: use PEAR::DB or PDO instead of our own switch
818 $res = mysql_query($stmt, $conn);
819 if ($res === false) {
820 $error = mysql_error();
824 $res = pg_query($conn, $stmt);
825 if ($res === false) {
826 $error = pg_last_error();
830 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
832 if ($res === false) {
833 updateStatus("ERROR ($error) for SQL '$stmt'");
840 function registerInitialUser($nickname, $password, $email)
842 define('STATUSNET', true);
843 define('LACONICA', true); // compatibility
845 require_once INSTALLDIR . '/lib/common.php';
847 $data = array('nickname' => $nickname,
848 'password' => $password,
849 'fullname' => $nickname);
851 $data['email'] = $email;
853 $user = User::register($data);
859 // give initial user carte blanche
861 $user->grantRole('owner');
862 $user->grantRole('moderator');
863 $user->grantRole('administrator');
869 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
871 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
872 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
873 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
875 <title>Install StatusNet</title>
876 <link rel="shortcut icon" href="favicon.ico"/>
877 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css" media="screen, projection, tv"/>
878 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css" /><![endif]-->
879 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css" /><![endif]-->
880 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css" /><![endif]-->
881 <script src="js/jquery.min.js"></script>
882 <script src="js/install.js"></script>
887 <address id="site_contact" class="vcard">
888 <a class="url home bookmark" href=".">
889 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
890 <span class="fn org">StatusNet</span>
896 <div id="content_inner">
897 <h1>Install StatusNet</h1>