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'
134 'name'=>'Net_Socket',
135 'pear'=>'Net_Socket',
136 'url'=>'http://pear.php.net/package/Net_Socket',
137 'deb'=>'php-net-socket',
138 'include'=>'Net/Socket.php',
139 'check_class'=>'Net_Socket'
144 'url'=>'http://pear.php.net/package/Net_SMTP',
145 'deb'=>'php-net-smtp',
146 'include'=>'Net/SMTP.php',
147 'check_class'=>'Net_SMTP'
152 'url'=>'http://pear.php.net/package/Net_URL',
153 'deb'=>'php-net-url',
154 'include'=>'Net/URL.php',
155 'check_class'=>'Net_URL'
160 'url'=>'http://pear.php.net/package/Net_URL2',
161 'include'=>'Net/URL2.php',
162 'check_class'=>'Net_URL2'
165 'name'=>'Services_oEmbed',
166 'pear'=>'Services_oEmbed',
167 'url'=>'http://pear.php.net/package/Services_oEmbed',
168 'include'=>'Services/oEmbed.php',
169 'check_class'=>'Services_oEmbed'
173 'url'=>'http://stomp.codehaus.org/PHP',
174 'include'=>'Stomp.php',
175 'check_class'=>'Stomp'
178 'name'=>'System_Command',
179 'pear'=>'System_Command',
180 'url'=>'http://pear.php.net/package/System_Command',
181 'include'=>'System/Command.php',
182 'check_class'=>'System_Command'
186 'url'=>'http://code.google.com/p/xmpphp',
187 'include'=>'XMPPHP/XMPP.php',
188 'check_class'=>'XMPPHP_XMPP'
191 'name'=>'PHP Markdown',
192 'url'=>'http://www.michelf.com/projects/php-markdown/',
193 'include'=>'markdown.php',
194 'check_class'=>'Markdown_Parser'
198 'url'=>'http://code.google.com/p/oauth-php',
199 'include'=>'OAuth.php',
200 'check_class'=>'OAuthRequest'
205 'url'=>'http://pear.php.net/package/Validate',
206 'include'=>'Validate.php',
207 'check_class'=>'Validate'
213 'check_module' => 'mysql', // mysqli?
214 'installer' => 'mysql_db_installer',
217 'name' => 'PostgreSQL',
218 'check_module' => 'pgsql',
219 'installer' => 'pgsql_db_installer',
224 * the actual installation.
225 * If call libraries are present, then install
231 if (!checkPrereqs()) {
235 if (!empty($_GET['checklibs'])) {
238 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
247 * checks if an external libary is present
249 * @param string $external_library Name of library
251 * @return boolean indicates if library present
253 function haveExternalLibrary($external_library)
255 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
258 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
261 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
267 // Attempt to include a PHP file and report if it worked, while
268 // suppressing the annoying warning messages on failure.
269 function haveIncludeFile($filename) {
270 $old = error_reporting(error_reporting() & ~E_WARNING);
271 $ok = include_once($filename);
272 error_reporting($old);
277 * Check if all is ready for installation
281 function checkPrereqs()
285 if (file_exists(INSTALLDIR.'/config.php')) {
286 printf('<p class="error">Config file "config.php" already exists.</p>');
290 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
291 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
295 $reqs = array('gd', 'curl',
296 'xmlwriter', 'mbstring','tidy');
298 foreach ($reqs as $req) {
299 if (!checkExtension($req)) {
300 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
304 // Make sure we have at least one database module available
306 $missingExtensions = array();
307 foreach ($dbModules as $type => $info) {
308 if (!checkExtension($info['check_module'])) {
309 $missingExtensions[] = $info['check_module'];
313 if (count($missingExtensions) == count($dbModules)) {
314 $req = implode(', ', $missingExtensions);
315 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
319 if (!is_writable(INSTALLDIR)) {
320 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
321 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
325 // Check the subdirs used for file uploads
326 $fileSubdirs = array('avatar', 'background', 'file');
327 foreach ($fileSubdirs as $fileSubdir) {
328 $fileFullPath = INSTALLDIR."/$fileSubdir/";
329 if (!is_writable($fileFullPath)) {
330 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
331 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
340 * Checks if a php extension is both installed and loaded
342 * @param string $name of extension to check
344 * @return boolean whether extension is installed and loaded
346 function checkExtension($name)
348 if (extension_loaded($name)) {
350 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
351 // dl will throw a fatal error if it's disabled or we're in safe mode.
352 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
353 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
354 if (PHP_SHLIB_SUFFIX == 'dll') {
355 $soname = "php_" . $soname;
364 * Show list of libraries
370 global $external_libraries;
371 $present_libraries=array();
372 $absent_libraries=array();
373 foreach ($external_libraries as $external_library) {
374 if (haveExternalLibrary($external_library)) {
375 $present_libraries[]=$external_library;
377 $absent_libraries[]=$external_library;
381 <div class="instructions">
382 <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
383 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
384 <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>
385 <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>
386 <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>
388 <h2>Absent Libraries</h2>
389 <ul id="absent_libraries">
391 foreach ($absent_libraries as $library) {
393 if (isset($library['url'])) {
394 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
396 echo htmlentities($library['name']);
399 if (isset($library['deb'])) {
400 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
402 if (isset($library['rpm'])) {
403 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
405 if (isset($library['pear'])) {
406 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
412 <h2>Installed Libraries</h2>
413 <ul id="present_libraries">
415 foreach ($present_libraries as $library) {
417 if (isset($library['url'])) {
418 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
420 echo htmlentities($library['name']);
433 $checked = 'checked="checked" '; // Check the first one which exists
434 foreach ($dbModules as $type => $info) {
435 if (checkExtension($info['check_module'])) {
436 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
444 <dl id="page_notice" class="system_notice">
447 <div class="instructions">
448 <p>Enter your database connection information below to initialize the database.</p>
449 <p>StatusNet bundles a number of libraries for ease of installation. <a href="?checklibs=true">You can see what bundled libraries you are using, versus what libraries are installed on your server.</a>
453 <form method="post" action="install.php" class="form_settings" id="form_install">
455 <legend>Connection settings</legend>
456 <ul class="form_data">
458 <label for="sitename">Site name</label>
459 <input type="text" id="sitename" name="sitename" />
460 <p class="form_guide">The name of your site</p>
463 <label for="fancy-enable">Fancy URLs</label>
464 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
465 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
466 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
469 <label for="host">Hostname</label>
470 <input type="text" id="host" name="host" />
471 <p class="form_guide">Database hostname</p>
475 <label for="dbtype">Type</label>
477 <p class="form_guide">Database type</p>
481 <label for="database">Name</label>
482 <input type="text" id="database" name="database" />
483 <p class="form_guide">Database name</p>
486 <label for="username">Username</label>
487 <input type="text" id="username" name="username" />
488 <p class="form_guide">Database username</p>
491 <label for="password">Password</label>
492 <input type="password" id="password" name="password" />
493 <p class="form_guide">Database password (optional)</p>
496 <input type="submit" name="submit" class="submit" value="Submit" />
503 function updateStatus($status, $error=false)
505 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
508 function handlePost()
510 $host = $_POST['host'];
511 $dbtype = $_POST['dbtype'];
512 $database = $_POST['database'];
513 $username = $_POST['username'];
514 $password = $_POST['password'];
515 $sitename = $_POST['sitename'];
516 $fancy = !empty($_POST['fancy']);
517 $server = $_SERVER['HTTP_HOST'];
518 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
521 <dl class="system_notice">
529 updateStatus("No hostname specified.", true);
533 if (empty($database)) {
534 updateStatus("No database specified.", true);
538 if (empty($username)) {
539 updateStatus("No username specified.", true);
543 if (empty($sitename)) {
544 updateStatus("No sitename specified.", true);
554 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
557 // database connection failed, do not move on to create config file.
561 updateStatus("Writing config file...");
562 $res = writeConf($sitename, $server, $path, $fancy, $db);
565 updateStatus("Can't write config file.", true);
571 TODO https needs to be considered
573 $link = "http://".$server.'/'.$path;
575 updateStatus("StatusNet has been installed at $link");
576 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
579 function Pgsql_Db_installer($host, $database, $username, $password)
581 $connstring = "dbname=$database host=$host user=$username";
583 //No password would mean trust authentication used.
584 if (!empty($password)) {
585 $connstring .= " password=$password";
587 updateStatus("Starting installation...");
588 updateStatus("Checking database...");
589 $conn = pg_connect($connstring);
591 if ($conn ===false) {
592 updateStatus("Failed to connect to database: $connstring");
597 //ensure database encoding is UTF8
598 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
599 if ($record->server_encoding != 'UTF8') {
600 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
605 updateStatus("Running database script...");
606 //wrap in transaction;
607 pg_query($conn, 'BEGIN');
608 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
610 if ($res === false) {
611 updateStatus("Can't run database script.", true);
615 foreach (array('sms_carrier' => 'SMS carrier',
616 'notice_source' => 'notice source',
617 'foreign_services' => 'foreign service')
619 updateStatus(sprintf("Adding %s data to database...", $name));
620 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
621 if ($res === false) {
622 updateStatus(sprintf("Can't run %d script.", $name), true);
627 pg_query($conn, 'COMMIT');
629 if (empty($password)) {
630 $sqlUrl = "pgsql://$username@$host/$database";
632 $sqlUrl = "pgsql://$username:$password@$host/$database";
635 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
640 function Mysql_Db_installer($host, $database, $username, $password)
642 updateStatus("Starting installation...");
643 updateStatus("Checking database...");
645 $conn = mysql_connect($host, $username, $password);
647 updateStatus("Can't connect to server '$host' as '$username'.", true);
651 updateStatus("Changing to database...");
652 $res = mysql_select_db($database, $conn);
654 updateStatus("Can't change to database.", true);
658 updateStatus("Running database script...");
659 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
660 if ($res === false) {
661 updateStatus("Can't run database script.", true);
665 foreach (array('sms_carrier' => 'SMS carrier',
666 'notice_source' => 'notice source',
667 'foreign_services' => 'foreign service')
669 updateStatus(sprintf("Adding %s data to database...", $name));
670 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
671 if ($res === false) {
672 updateStatus(sprintf("Can't run %d script.", $name), true);
678 $sqlUrl = "mysqli://$username:$password@$host/$database";
679 $db = array('type' => 'mysql', 'database' => $sqlUrl);
683 function writeConf($sitename, $server, $path, $fancy, $db)
685 // assemble configuration file in a string
687 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
690 "\$config['site']['name'] = '$sitename';\n\n".
693 "\$config['site']['server'] = '$server';\n".
694 "\$config['site']['path'] = '$path'; \n\n".
696 // checks if fancy URLs are enabled
697 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
700 "\$config['db']['database'] = '{$db['database']}';\n\n".
701 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
702 "\$config['db']['type'] = '{$db['type']}';\n\n";
703 // write configuration file out to install directory
704 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
710 * Install schema into the database
712 * @param string $filename location of database schema file
713 * @param dbconn $conn connection to database
714 * @param string $type type of database, currently mysql or pgsql
716 * @return boolean - indicating success or failure
718 function runDbScript($filename, $conn, $type = 'mysqli')
720 $sql = trim(file_get_contents($filename));
721 $stmts = explode(';', $sql);
722 foreach ($stmts as $stmt) {
724 if (!mb_strlen($stmt)) {
727 // FIXME: use PEAR::DB or PDO instead of our own switch
730 $res = mysql_query($stmt, $conn);
731 if ($res === false) {
732 $error = mysql_error();
736 $res = pg_query($conn, $stmt);
737 if ($res === false) {
738 $error = pg_last_error();
742 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
744 if ($res === false) {
745 updateStatus("ERROR ($error) for SQL '$stmt'");
753 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
755 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
756 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
757 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
759 <title>Install StatusNet</title>
760 <link rel="shortcut icon" href="favicon.ico"/>
761 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
762 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
763 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
764 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
765 <script src="js/jquery.min.js"></script>
766 <script src="js/install.js"></script>
771 <address id="site_contact" class="vcard">
772 <a class="url home bookmark" href=".">
773 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
774 <span class="fn org">StatusNet</span>
780 <h1>Install StatusNet</h1>