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 <form method="post" action="install.php" class="form_settings" id="form_install">
479 <fieldset id="settings_site">
480 <legend>Site settings</legend>
481 <ul class="form_data">
483 <label for="sitename">Site name</label>
484 <input type="text" id="sitename" name="sitename" value="{$post->value('sitename')}" />
485 <p class="form_guide">The name of your site</p>
488 <label for="fancy-enable">Fancy URLs</label>
489 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
490 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
491 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
496 <fieldset id="settings_db">
497 <legend>Database settings</legend>
498 <ul class="form_data">
500 <label for="host">Hostname</label>
501 <input type="text" id="host" name="host" value="{$post->value('host')}" />
502 <p class="form_guide">Database hostname</p>
505 <label for="dbtype">Type</label>
507 <p class="form_guide">Database type</p>
510 <label for="database">Name</label>
511 <input type="text" id="database" name="database" value="{$post->value('database')}" />
512 <p class="form_guide">Database name</p>
515 <label for="dbusername">DB username</label>
516 <input type="text" id="dbusername" name="dbusername" value="{$post->value('dbusername')}" />
517 <p class="form_guide">Database username</p>
520 <label for="dbpassword">DB password</label>
521 <input type="password" id="dbpassword" name="dbpassword" value="{$post->value('dbpassword')}" />
522 <p class="form_guide">Database password (optional)</p>
527 <fieldset id="settings_admin">
528 <legend>Administrator settings</legend>
529 <ul class="form_data">
531 <label for="admin_nickname">Administrator nickname</label>
532 <input type="text" id="admin_nickname" name="admin_nickname" value="{$post->value('admin_nickname')}" />
533 <p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
536 <label for="admin_password">Administrator password</label>
537 <input type="password" id="admin_password" name="admin_password" value="{$post->value('admin_password')}" />
538 <p class="form_guide">Password for the initial StatusNet user (administrator)</p>
541 <label for="admin_password2">Confirm password</label>
542 <input type="password" id="admin_password2" name="admin_password2" value="{$post->value('admin_password2')}" />
545 <label for="admin_email">Administrator e-mail</label>
546 <input id="admin_email" name="admin_email" value="{$post->value('admin_email')}" />
547 <p class="form_guide">Optional email address for the initial StatusNet user (administrator)</p>
551 <input type="submit" name="submit" class="submit" value="Submit" />
558 function updateStatus($status, $error=false)
560 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
563 function handlePost()
565 $host = $_POST['host'];
566 $dbtype = $_POST['dbtype'];
567 $database = $_POST['database'];
568 $username = $_POST['dbusername'];
569 $password = $_POST['dbpassword'];
570 $sitename = $_POST['sitename'];
571 $fancy = !empty($_POST['fancy']);
573 $adminNick = $_POST['admin_nickname'];
574 $adminPass = $_POST['admin_password'];
575 $adminPass2 = $_POST['admin_password2'];
576 $adminEmail = $_POST['admin_email'];
578 $server = $_SERVER['HTTP_HOST'];
579 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
582 <dl class="system_notice">
590 updateStatus("No hostname specified.", true);
594 if (empty($database)) {
595 updateStatus("No database specified.", true);
599 if (empty($username)) {
600 updateStatus("No username specified.", true);
604 if (empty($sitename)) {
605 updateStatus("No sitename specified.", true);
609 if (empty($adminNick)) {
610 updateStatus("No initial StatusNet user nickname specified.", true);
614 if (empty($adminPass)) {
615 updateStatus("No initial StatusNet user password specified.", true);
619 if ($adminPass != $adminPass2) {
620 updateStatus("Administrator passwords do not match. Did you mistype?", true);
630 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
633 // database connection failed, do not move on to create config file.
637 updateStatus("Writing config file...");
638 $res = writeConf($sitename, $server, $path, $fancy, $db);
641 updateStatus("Can't write config file.", true);
646 // Okay, cross fingers and try to register an initial user
647 if (registerInitialUser($adminNick, $adminPass, $adminEmail)) {
649 "An initial user with the administrator role has been created."
653 "Could not create initial StatusNet user (administrator).",
661 TODO https needs to be considered
663 $link = "http://".$server.'/'.$path;
665 updateStatus("StatusNet has been installed at $link");
667 "<strong>DONE!</strong> You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick'). If this is your first StatusNet install, you may want to poke around our <a href='http://status.net/wiki/Getting_started'>Getting Started guide</a>."
671 function Pgsql_Db_installer($host, $database, $username, $password)
673 $connstring = "dbname=$database host=$host user=$username";
675 //No password would mean trust authentication used.
676 if (!empty($password)) {
677 $connstring .= " password=$password";
679 updateStatus("Starting installation...");
680 updateStatus("Checking database...");
681 $conn = pg_connect($connstring);
683 if ($conn ===false) {
684 updateStatus("Failed to connect to database: $connstring");
689 //ensure database encoding is UTF8
690 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
691 if ($record->server_encoding != 'UTF8') {
692 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
697 updateStatus("Running database script...");
698 //wrap in transaction;
699 pg_query($conn, 'BEGIN');
700 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
702 if ($res === false) {
703 updateStatus("Can't run database script.", true);
707 foreach (array('sms_carrier' => 'SMS carrier',
708 'notice_source' => 'notice source',
709 'foreign_services' => 'foreign service')
711 updateStatus(sprintf("Adding %s data to database...", $name));
712 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
713 if ($res === false) {
714 updateStatus(sprintf("Can't run %d script.", $name), true);
719 pg_query($conn, 'COMMIT');
721 if (empty($password)) {
722 $sqlUrl = "pgsql://$username@$host/$database";
724 $sqlUrl = "pgsql://$username:$password@$host/$database";
727 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
732 function Mysql_Db_installer($host, $database, $username, $password)
734 updateStatus("Starting installation...");
735 updateStatus("Checking database...");
737 $conn = mysql_connect($host, $username, $password);
739 updateStatus("Can't connect to server '$host' as '$username'.", true);
743 updateStatus("Changing to database...");
744 $res = mysql_select_db($database, $conn);
746 updateStatus("Can't change to database.", true);
750 updateStatus("Running database script...");
751 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
752 if ($res === false) {
753 updateStatus("Can't run database script.", true);
757 foreach (array('sms_carrier' => 'SMS carrier',
758 'notice_source' => 'notice source',
759 'foreign_services' => 'foreign service')
761 updateStatus(sprintf("Adding %s data to database...", $name));
762 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
763 if ($res === false) {
764 updateStatus(sprintf("Can't run %d script.", $name), true);
770 $sqlUrl = "mysqli://$username:$password@$host/$database";
771 $db = array('type' => 'mysql', 'database' => $sqlUrl);
775 function writeConf($sitename, $server, $path, $fancy, $db)
777 // assemble configuration file in a string
779 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
782 "\$config['site']['name'] = '$sitename';\n\n".
785 "\$config['site']['server'] = '$server';\n".
786 "\$config['site']['path'] = '$path'; \n\n".
788 // checks if fancy URLs are enabled
789 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
792 "\$config['db']['database'] = '{$db['database']}';\n\n".
793 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
794 "\$config['db']['type'] = '{$db['type']}';\n\n";
795 // write configuration file out to install directory
796 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
802 * Install schema into the database
804 * @param string $filename location of database schema file
805 * @param dbconn $conn connection to database
806 * @param string $type type of database, currently mysql or pgsql
808 * @return boolean - indicating success or failure
810 function runDbScript($filename, $conn, $type = 'mysqli')
812 $sql = trim(file_get_contents($filename));
813 $stmts = explode(';', $sql);
814 foreach ($stmts as $stmt) {
816 if (!mb_strlen($stmt)) {
819 // FIXME: use PEAR::DB or PDO instead of our own switch
822 $res = mysql_query($stmt, $conn);
823 if ($res === false) {
824 $error = mysql_error();
828 $res = pg_query($conn, $stmt);
829 if ($res === false) {
830 $error = pg_last_error();
834 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
836 if ($res === false) {
837 updateStatus("ERROR ($error) for SQL '$stmt'");
844 function registerInitialUser($nickname, $password, $email)
846 define('STATUSNET', true);
847 define('LACONICA', true); // compatibility
849 require_once INSTALLDIR . '/lib/common.php';
851 $data = array('nickname' => $nickname,
852 'password' => $password,
853 'fullname' => $nickname);
855 $data['email'] = $email;
857 $user = User::register($data);
863 // give initial user carte blanche
865 $user->grantRole('owner');
866 $user->grantRole('moderator');
867 $user->grantRole('administrator');
873 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
875 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
876 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
877 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
879 <title>Install StatusNet</title>
880 <link rel="shortcut icon" href="favicon.ico"/>
881 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css" media="screen, projection, tv"/>
882 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css" /><![endif]-->
883 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css" /><![endif]-->
884 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css" /><![endif]-->
885 <script src="js/jquery.min.js"></script>
886 <script src="js/install.js"></script>
891 <address id="site_contact" class="vcard">
892 <a class="url home bookmark" href=".">
893 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
894 <span class="fn org">StatusNet</span>
900 <div id="content_inner">
901 <h1>Install StatusNet</h1>