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']);
442 $checked = 'checked="checked" '; // Check the first one which exists
443 foreach ($dbModules as $type => $info) {
444 if (checkExtension($info['check_module'])) {
445 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
453 <dl id="page_notice" class="system_notice">
456 <div class="instructions">
457 <p>Enter your database connection information below to initialize the database.</p>
461 <form method="post" action="install.php" class="form_settings" id="form_install">
463 <legend>Connection settings</legend>
464 <ul class="form_data">
466 <label for="sitename">Site name</label>
467 <input type="text" id="sitename" name="sitename" />
468 <p class="form_guide">The name of your site</p>
471 <label for="fancy-enable">Fancy URLs</label>
472 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
473 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
474 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
477 <label for="host">Hostname</label>
478 <input type="text" id="host" name="host" />
479 <p class="form_guide">Database hostname</p>
483 <label for="dbtype">Type</label>
485 <p class="form_guide">Database type</p>
489 <label for="database">Name</label>
490 <input type="text" id="database" name="database" />
491 <p class="form_guide">Database name</p>
494 <label for="username">DB username</label>
495 <input type="text" id="username" name="username" />
496 <p class="form_guide">Database username</p>
499 <label for="password">DB password</label>
500 <input type="password" id="password" name="password" />
501 <p class="form_guide">Database password (optional)</p>
504 <label for="admin_nickname">Administrator nickname</label>
505 <input type="text" id="admin_nickname" name="admin_nickname" />
506 <p class="form_guide">Nickname for the initial StatusNet user (administrator)</p>
509 <label for="initial_user_password">Administrator password</label>
510 <input type="password" id="admin_password" name="admin_password" />
511 <p class="form_guide">Password for the initial StatusNet user (administrator)</p>
514 <input type="submit" name="submit" class="submit" value="Submit" />
521 function updateStatus($status, $error=false)
523 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
526 function handlePost()
528 $host = $_POST['host'];
529 $dbtype = $_POST['dbtype'];
530 $database = $_POST['database'];
531 $username = $_POST['username'];
532 $password = $_POST['password'];
533 $sitename = $_POST['sitename'];
534 $fancy = !empty($_POST['fancy']);
536 $adminNick = $_POST['admin_nickname'];
537 $adminPass = $_POST['admin_password'];
539 $server = $_SERVER['HTTP_HOST'];
540 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
543 <dl class="system_notice">
551 updateStatus("No hostname specified.", true);
555 if (empty($database)) {
556 updateStatus("No database specified.", true);
560 if (empty($username)) {
561 updateStatus("No username specified.", true);
565 if (empty($sitename)) {
566 updateStatus("No sitename specified.", true);
570 if (empty($adminNick)) {
571 updateStatus("No initial StatusNet user nickname specified.", true);
575 if (empty($adminPass)) {
576 updateStatus("No initial StatusNet user password specified.", true);
586 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
589 // database connection failed, do not move on to create config file.
593 updateStatus("Writing config file...");
594 $res = writeConf($sitename, $server, $path, $fancy, $db);
597 updateStatus("Can't write config file.", true);
602 // Okay, cross fingers and try to register an initial user
603 if (registerInitialUser($adminNick, $adminPass)) {
605 "An initial user with the administrator role has been created."
609 "Could not create initial StatusNet user (administrator).",
617 TODO https needs to be considered
619 $link = "http://".$server.'/'.$path;
621 updateStatus("StatusNet has been installed at $link");
623 "You can visit your <a href='$link'>new StatusNet site</a> (login as '$adminNick')."
627 function Pgsql_Db_installer($host, $database, $username, $password)
629 $connstring = "dbname=$database host=$host user=$username";
631 //No password would mean trust authentication used.
632 if (!empty($password)) {
633 $connstring .= " password=$password";
635 updateStatus("Starting installation...");
636 updateStatus("Checking database...");
637 $conn = pg_connect($connstring);
639 if ($conn ===false) {
640 updateStatus("Failed to connect to database: $connstring");
645 //ensure database encoding is UTF8
646 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
647 if ($record->server_encoding != 'UTF8') {
648 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
653 updateStatus("Running database script...");
654 //wrap in transaction;
655 pg_query($conn, 'BEGIN');
656 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
658 if ($res === false) {
659 updateStatus("Can't run database script.", true);
663 foreach (array('sms_carrier' => 'SMS carrier',
664 'notice_source' => 'notice source',
665 'foreign_services' => 'foreign service')
667 updateStatus(sprintf("Adding %s data to database...", $name));
668 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
669 if ($res === false) {
670 updateStatus(sprintf("Can't run %d script.", $name), true);
675 pg_query($conn, 'COMMIT');
677 if (empty($password)) {
678 $sqlUrl = "pgsql://$username@$host/$database";
680 $sqlUrl = "pgsql://$username:$password@$host/$database";
683 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
688 function Mysql_Db_installer($host, $database, $username, $password)
690 updateStatus("Starting installation...");
691 updateStatus("Checking database...");
693 $conn = mysql_connect($host, $username, $password);
695 updateStatus("Can't connect to server '$host' as '$username'.", true);
699 updateStatus("Changing to database...");
700 $res = mysql_select_db($database, $conn);
702 updateStatus("Can't change to database.", true);
706 updateStatus("Running database script...");
707 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
708 if ($res === false) {
709 updateStatus("Can't run database script.", true);
713 foreach (array('sms_carrier' => 'SMS carrier',
714 'notice_source' => 'notice source',
715 'foreign_services' => 'foreign service')
717 updateStatus(sprintf("Adding %s data to database...", $name));
718 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
719 if ($res === false) {
720 updateStatus(sprintf("Can't run %d script.", $name), true);
726 $sqlUrl = "mysqli://$username:$password@$host/$database";
727 $db = array('type' => 'mysql', 'database' => $sqlUrl);
731 function writeConf($sitename, $server, $path, $fancy, $db)
733 // assemble configuration file in a string
735 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
738 "\$config['site']['name'] = '$sitename';\n\n".
741 "\$config['site']['server'] = '$server';\n".
742 "\$config['site']['path'] = '$path'; \n\n".
744 // checks if fancy URLs are enabled
745 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
748 "\$config['db']['database'] = '{$db['database']}';\n\n".
749 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
750 "\$config['db']['type'] = '{$db['type']}';\n\n";
751 // write configuration file out to install directory
752 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
758 * Install schema into the database
760 * @param string $filename location of database schema file
761 * @param dbconn $conn connection to database
762 * @param string $type type of database, currently mysql or pgsql
764 * @return boolean - indicating success or failure
766 function runDbScript($filename, $conn, $type = 'mysqli')
768 $sql = trim(file_get_contents($filename));
769 $stmts = explode(';', $sql);
770 foreach ($stmts as $stmt) {
772 if (!mb_strlen($stmt)) {
775 // FIXME: use PEAR::DB or PDO instead of our own switch
778 $res = mysql_query($stmt, $conn);
779 if ($res === false) {
780 $error = mysql_error();
784 $res = pg_query($conn, $stmt);
785 if ($res === false) {
786 $error = pg_last_error();
790 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
792 if ($res === false) {
793 updateStatus("ERROR ($error) for SQL '$stmt'");
800 function registerInitialUser($nickname, $password)
802 define('STATUSNET', true);
803 define('LACONICA', true); // compatibility
805 require_once INSTALLDIR . '/lib/common.php';
807 $user = User::register(
808 array('nickname' => $nickname,
809 'password' => $password,
810 'fullname' => $nickname
818 // give initial user carte blanche
820 $user->grantRole('owner');
821 $user->grantRole('moderator');
822 $user->grantRole('administrator');
828 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
830 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
831 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
832 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
834 <title>Install StatusNet</title>
835 <link rel="shortcut icon" href="favicon.ico"/>
836 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css" media="screen, projection, tv"/>
837 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css" /><![endif]-->
838 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css" /><![endif]-->
839 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css" /><![endif]-->
840 <script src="js/jquery.min.js"></script>
841 <script src="js/install.js"></script>
846 <address id="site_contact" class="vcard">
847 <a class="url home bookmark" href=".">
848 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
849 <span class="fn org">StatusNet</span>
855 <div id="content_inner">
856 <h1>Install StatusNet</h1>