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 * @license GNU Affero General Public License http://www.gnu.org/licenses/
36 * @link http://status.net
39 define('INSTALLDIR', dirname(__FILE__));
41 $external_libraries=array(
44 'url'=>'http://us.php.net/manual/en/book.gettext.php',
45 'check_function'=>'gettext'
49 'url'=>'http://pear.php.net/',
51 'include'=>'PEAR.php',
57 'url'=>'http://pear.php.net/package/DB',
59 'include'=>'DB/common.php',
60 'check_class'=>'DB_common'
63 'name'=>'DB_DataObject',
64 'pear'=>'DB_DataObject',
65 'url'=>'http://pear.php.net/package/DB_DataObject',
66 'include'=>'DB/DataObject.php',
67 'check_class'=>'DB_DataObject'
70 'name'=>'Console_Getopt',
71 'pear'=>'Console_Getopt',
72 'url'=>'http://pear.php.net/package/Console_Getopt',
73 'include'=>'Console/Getopt.php',
74 'check_class'=>'Console_Getopt'
77 'name'=>'Facebook API',
78 'url'=>'http://developers.facebook.com/',
79 'include'=>'facebook/facebook.php',
80 'check_class'=>'Facebook'
84 'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
85 'include'=>'htmLawed/htmLawed.php',
86 'check_function'=>'htmLawed'
89 'name'=>'HTTP_Request',
90 'pear'=>'HTTP_Request',
91 'url'=>'http://pear.php.net/package/HTTP_Request',
92 'deb'=>'php-http-request',
93 'include'=>'HTTP/Request.php',
94 'check_class'=>'HTTP_Request'
97 'name'=>'HTTP_Request2',
98 'pear'=>'HTTP_Request2',
99 'url'=>'http://pear.php.net/package/HTTP_Request2',
100 'include'=>'HTTP/Request2.php',
101 'check_class'=>'HTTP_Request2'
106 'url'=>'http://pear.php.net/package/Mail',
108 'include'=>'Mail.php',
109 'check_class'=>'Mail'
112 'name'=>'Mail_mimeDecode',
113 'pear'=>'Mail_mimeDecode',
114 'url'=>'http://pear.php.net/package/Mail_mimeDecode',
115 'deb'=>'php-mail-mimedecode',
116 'include'=>'Mail/mimeDecode.php',
117 'check_class'=>'Mail_mimeDecode'
122 'url'=>'http://pear.php.net/package/Mime_Type',
123 'include'=>'MIME/Type.php',
124 'check_class'=>'Mime_Type'
127 'name'=>'Net_URL_Mapper',
128 'pear'=>'Net_URL_Mapper',
129 'url'=>'http://pear.php.net/package/Net_URL_Mapper',
130 'include'=>'Net/URL/Mapper.php',
131 'check_class'=>'Net_URL_Mapper'
136 'url'=>'http://pear.php.net/package/Net_LDAP2',
137 'deb'=>'php-net-ldap2',
138 'include'=>'Net/LDAP2.php',
139 'check_class'=>'Net_LDAP2'
142 'name'=>'Net_Socket',
143 'pear'=>'Net_Socket',
144 'url'=>'http://pear.php.net/package/Net_Socket',
145 'deb'=>'php-net-socket',
146 'include'=>'Net/Socket.php',
147 'check_class'=>'Net_Socket'
152 'url'=>'http://pear.php.net/package/Net_SMTP',
153 'deb'=>'php-net-smtp',
154 'include'=>'Net/SMTP.php',
155 'check_class'=>'Net_SMTP'
160 'url'=>'http://pear.php.net/package/Net_URL',
161 'deb'=>'php-net-url',
162 'include'=>'Net/URL.php',
163 'check_class'=>'Net_URL'
168 'url'=>'http://pear.php.net/package/Net_URL2',
169 'include'=>'Net/URL2.php',
170 'check_class'=>'Net_URL2'
173 'name'=>'Services_oEmbed',
174 'pear'=>'Services_oEmbed',
175 'url'=>'http://pear.php.net/package/Services_oEmbed',
176 'include'=>'Services/oEmbed.php',
177 'check_class'=>'Services_oEmbed'
181 'url'=>'http://stomp.codehaus.org/PHP',
182 'include'=>'Stomp.php',
183 'check_class'=>'Stomp'
186 'name'=>'System_Command',
187 'pear'=>'System_Command',
188 'url'=>'http://pear.php.net/package/System_Command',
189 'include'=>'System/Command.php',
190 'check_class'=>'System_Command'
194 'url'=>'http://code.google.com/p/xmpphp',
195 'include'=>'XMPPHP/XMPP.php',
196 'check_class'=>'XMPPHP_XMPP'
199 'name'=>'PHP Markdown',
200 'url'=>'http://www.michelf.com/projects/php-markdown/',
201 'include'=>'markdown.php',
202 'check_class'=>'Markdown_Parser'
206 'url'=>'http://code.google.com/p/oauth-php',
207 'include'=>'OAuth.php',
208 'check_class'=>'OAuthRequest'
213 'url'=>'http://pear.php.net/package/Validate',
214 'include'=>'Validate.php',
215 'check_class'=>'Validate'
221 'check_module' => 'mysql', // mysqli?
222 'installer' => 'mysql_db_installer',
225 'name' => 'PostgreSQL',
226 'check_module' => 'pgsql',
227 'installer' => 'pgsql_db_installer',
232 * the actual installation.
233 * If call libraries are present, then install
239 if (!checkPrereqs()) {
243 if (!empty($_GET['checklibs'])) {
246 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
255 * checks if an external libary is present
257 * @param string $external_library Name of library
259 * @return boolean indicates if library present
261 function haveExternalLibrary($external_library)
263 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
266 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
269 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
275 // Attempt to include a PHP file and report if it worked, while
276 // suppressing the annoying warning messages on failure.
277 function haveIncludeFile($filename) {
278 $old = error_reporting(error_reporting() & ~E_WARNING);
279 $ok = include_once($filename);
280 error_reporting($old);
285 * Check if all is ready for installation
289 function checkPrereqs()
293 if (file_exists(INSTALLDIR.'/config.php')) {
294 printf('<p class="error">Config file "config.php" already exists.</p>');
298 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
299 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
303 $reqs = array('gd', 'curl',
304 'xmlwriter', 'mbstring', 'xml', 'dom', 'simplexml');
306 foreach ($reqs as $req) {
307 if (!checkExtension($req)) {
308 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
312 // Make sure we have at least one database module available
314 $missingExtensions = array();
315 foreach ($dbModules as $type => $info) {
316 if (!checkExtension($info['check_module'])) {
317 $missingExtensions[] = $info['check_module'];
321 if (count($missingExtensions) == count($dbModules)) {
322 $req = implode(', ', $missingExtensions);
323 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
327 if (!is_writable(INSTALLDIR)) {
328 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
329 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
333 // Check the subdirs used for file uploads
334 $fileSubdirs = array('avatar', 'background', 'file');
335 foreach ($fileSubdirs as $fileSubdir) {
336 $fileFullPath = INSTALLDIR."/$fileSubdir/";
337 if (!is_writable($fileFullPath)) {
338 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
339 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
348 * Checks if a php extension is both installed and loaded
350 * @param string $name of extension to check
352 * @return boolean whether extension is installed and loaded
354 function checkExtension($name)
356 if (extension_loaded($name)) {
358 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
359 // dl will throw a fatal error if it's disabled or we're in safe mode.
360 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
361 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
362 if (PHP_SHLIB_SUFFIX == 'dll') {
363 $soname = "php_" . $soname;
372 * Show list of libraries
378 global $external_libraries;
379 $present_libraries=array();
380 $absent_libraries=array();
381 foreach ($external_libraries as $external_library) {
382 if (haveExternalLibrary($external_library)) {
383 $present_libraries[]=$external_library;
385 $absent_libraries[]=$external_library;
389 <div class="instructions">
390 <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
391 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
392 <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>
393 <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>
394 <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>
396 <h2>Absent Libraries</h2>
397 <ul id="absent_libraries">
399 foreach ($absent_libraries as $library) {
401 if (isset($library['url'])) {
402 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
404 echo htmlentities($library['name']);
407 if (isset($library['deb'])) {
408 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
410 if (isset($library['rpm'])) {
411 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
413 if (isset($library['pear'])) {
414 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
420 <h2>Installed Libraries</h2>
421 <ul id="present_libraries">
423 foreach ($present_libraries as $library) {
425 if (isset($library['url'])) {
426 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
428 echo htmlentities($library['name']);
441 $checked = 'checked="checked" '; // Check the first one which exists
442 foreach ($dbModules as $type => $info) {
443 if (checkExtension($info['check_module'])) {
444 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
452 <dl id="page_notice" class="system_notice">
455 <div class="instructions">
456 <p>Enter your database connection information below to initialize the database.</p>
460 <form method="post" action="install.php" class="form_settings" id="form_install">
462 <legend>Connection settings</legend>
463 <ul class="form_data">
465 <label for="sitename">Site name</label>
466 <input type="text" id="sitename" name="sitename" />
467 <p class="form_guide">The name of your site</p>
470 <label for="fancy-enable">Fancy URLs</label>
471 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
472 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
473 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
476 <label for="host">Hostname</label>
477 <input type="text" id="host" name="host" />
478 <p class="form_guide">Database hostname</p>
482 <label for="dbtype">Type</label>
484 <p class="form_guide">Database type</p>
488 <label for="database">Name</label>
489 <input type="text" id="database" name="database" />
490 <p class="form_guide">Database name</p>
493 <label for="username">Username</label>
494 <input type="text" id="username" name="username" />
495 <p class="form_guide">Database username</p>
498 <label for="password">Password</label>
499 <input type="password" id="password" name="password" />
500 <p class="form_guide">Database password (optional)</p>
503 <input type="submit" name="submit" class="submit" value="Submit" />
510 function updateStatus($status, $error=false)
512 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
515 function handlePost()
517 $host = $_POST['host'];
518 $dbtype = $_POST['dbtype'];
519 $database = $_POST['database'];
520 $username = $_POST['username'];
521 $password = $_POST['password'];
522 $sitename = $_POST['sitename'];
523 $fancy = !empty($_POST['fancy']);
524 $server = $_SERVER['HTTP_HOST'];
525 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
528 <dl class="system_notice">
536 updateStatus("No hostname specified.", true);
540 if (empty($database)) {
541 updateStatus("No database specified.", true);
545 if (empty($username)) {
546 updateStatus("No username specified.", true);
550 if (empty($sitename)) {
551 updateStatus("No sitename specified.", true);
561 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
564 // database connection failed, do not move on to create config file.
568 updateStatus("Writing config file...");
569 $res = writeConf($sitename, $server, $path, $fancy, $db);
572 updateStatus("Can't write config file.", true);
578 TODO https needs to be considered
580 $link = "http://".$server.'/'.$path;
582 updateStatus("StatusNet has been installed at $link");
583 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
586 function Pgsql_Db_installer($host, $database, $username, $password)
588 $connstring = "dbname=$database host=$host user=$username";
590 //No password would mean trust authentication used.
591 if (!empty($password)) {
592 $connstring .= " password=$password";
594 updateStatus("Starting installation...");
595 updateStatus("Checking database...");
596 $conn = pg_connect($connstring);
598 if ($conn ===false) {
599 updateStatus("Failed to connect to database: $connstring");
604 //ensure database encoding is UTF8
605 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
606 if ($record->server_encoding != 'UTF8') {
607 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
612 updateStatus("Running database script...");
613 //wrap in transaction;
614 pg_query($conn, 'BEGIN');
615 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
617 if ($res === false) {
618 updateStatus("Can't run database script.", true);
622 foreach (array('sms_carrier' => 'SMS carrier',
623 'notice_source' => 'notice source',
624 'foreign_services' => 'foreign service')
626 updateStatus(sprintf("Adding %s data to database...", $name));
627 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
628 if ($res === false) {
629 updateStatus(sprintf("Can't run %d script.", $name), true);
634 pg_query($conn, 'COMMIT');
636 if (empty($password)) {
637 $sqlUrl = "pgsql://$username@$host/$database";
639 $sqlUrl = "pgsql://$username:$password@$host/$database";
642 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
647 function Mysql_Db_installer($host, $database, $username, $password)
649 updateStatus("Starting installation...");
650 updateStatus("Checking database...");
652 $conn = mysql_connect($host, $username, $password);
654 updateStatus("Can't connect to server '$host' as '$username'.", true);
658 updateStatus("Changing to database...");
659 $res = mysql_select_db($database, $conn);
661 updateStatus("Can't change to database.", true);
665 updateStatus("Running database script...");
666 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
667 if ($res === false) {
668 updateStatus("Can't run database script.", true);
672 foreach (array('sms_carrier' => 'SMS carrier',
673 'notice_source' => 'notice source',
674 'foreign_services' => 'foreign service')
676 updateStatus(sprintf("Adding %s data to database...", $name));
677 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
678 if ($res === false) {
679 updateStatus(sprintf("Can't run %d script.", $name), true);
685 $sqlUrl = "mysqli://$username:$password@$host/$database";
686 $db = array('type' => 'mysql', 'database' => $sqlUrl);
690 function writeConf($sitename, $server, $path, $fancy, $db)
692 // assemble configuration file in a string
694 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
697 "\$config['site']['name'] = '$sitename';\n\n".
700 "\$config['site']['server'] = '$server';\n".
701 "\$config['site']['path'] = '$path'; \n\n".
703 // checks if fancy URLs are enabled
704 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
707 "\$config['db']['database'] = '{$db['database']}';\n\n".
708 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
709 "\$config['db']['type'] = '{$db['type']}';\n\n";
710 // write configuration file out to install directory
711 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
717 * Install schema into the database
719 * @param string $filename location of database schema file
720 * @param dbconn $conn connection to database
721 * @param string $type type of database, currently mysql or pgsql
723 * @return boolean - indicating success or failure
725 function runDbScript($filename, $conn, $type = 'mysqli')
727 $sql = trim(file_get_contents($filename));
728 $stmts = explode(';', $sql);
729 foreach ($stmts as $stmt) {
731 if (!mb_strlen($stmt)) {
734 // FIXME: use PEAR::DB or PDO instead of our own switch
737 $res = mysql_query($stmt, $conn);
738 if ($res === false) {
739 $error = mysql_error();
743 $res = pg_query($conn, $stmt);
744 if ($res === false) {
745 $error = pg_last_error();
749 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
751 if ($res === false) {
752 updateStatus("ERROR ($error) for SQL '$stmt'");
760 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
762 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
763 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
764 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
766 <title>Install StatusNet</title>
767 <link rel="shortcut icon" href="favicon.ico"/>
768 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
769 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
770 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
771 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
772 <script src="js/jquery.min.js"></script>
773 <script src="js/install.js"></script>
778 <address id="site_contact" class="vcard">
779 <a class="url home bookmark" href=".">
780 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
781 <span class="fn org">StatusNet</span>
787 <h1>Install StatusNet</h1>