3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2009, StatusNet, Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @category Installation
20 * @package Installation
22 * @author Adrian Lang <mail@adrianlang.de>
23 * @author Brenda Wallace <shiny@cpan.org>
24 * @author Brett Taylor <brett@webfroot.co.nz>
25 * @author Brion Vibber <brion@pobox.com>
26 * @author CiaranG <ciaran@ciarang.com>
27 * @author Craig Andrews <candrews@integralblue.com>
28 * @author Eric Helgeson <erichelgeson@gmail.com>
29 * @author Evan Prodromou <evan@status.net>
30 * @author Robin Millette <millette@controlyourself.ca>
31 * @author Sarven Capadisli <csarven@status.net>
32 * @author Tom Adams <tom@holizz.com>
33 * @license GNU Affero General Public License http://www.gnu.org/licenses/
35 * @link http://status.net
38 define('INSTALLDIR', dirname(__FILE__));
40 $external_libraries=array(
43 'url'=>'http://us.php.net/manual/en/book.gettext.php',
44 'check_function'=>'gettext'
48 'url'=>'http://pear.php.net/',
50 'include'=>'PEAR.php',
56 'url'=>'http://pear.php.net/package/DB',
58 'include'=>'DB/common.php',
59 'check_class'=>'DB_common'
62 'name'=>'DB_DataObject',
63 'pear'=>'DB_DataObject',
64 'url'=>'http://pear.php.net/package/DB_DataObject',
65 'include'=>'DB/DataObject.php',
66 'check_class'=>'DB_DataObject'
69 'name'=>'Console_Getopt',
70 'pear'=>'Console_Getopt',
71 'url'=>'http://pear.php.net/package/Console_Getopt',
72 'include'=>'Console/Getopt.php',
73 'check_class'=>'Console_Getopt'
76 'name'=>'Facebook API',
77 'url'=>'http://developers.facebook.com/',
78 'include'=>'facebook/facebook.php',
79 'check_class'=>'Facebook'
83 'url'=>'http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed',
84 'include'=>'htmLawed/htmLawed.php',
85 'check_function'=>'htmLawed'
88 'name'=>'HTTP_Request',
89 'pear'=>'HTTP_Request',
90 'url'=>'http://pear.php.net/package/HTTP_Request',
91 'deb'=>'php-http-request',
92 'include'=>'HTTP/Request.php',
93 'check_class'=>'HTTP_Request'
96 'name'=>'HTTP_Request2',
97 'pear'=>'HTTP_Request2',
98 'url'=>'http://pear.php.net/package/HTTP_Request2',
99 'include'=>'HTTP/Request2.php',
100 'check_class'=>'HTTP_Request2'
105 'url'=>'http://pear.php.net/package/Mail',
107 'include'=>'Mail.php',
108 'check_class'=>'Mail'
111 'name'=>'Mail_mimeDecode',
112 'pear'=>'Mail_mimeDecode',
113 'url'=>'http://pear.php.net/package/Mail_mimeDecode',
114 'deb'=>'php-mail-mimedecode',
115 'include'=>'Mail/mimeDecode.php',
116 'check_class'=>'Mail_mimeDecode'
121 'url'=>'http://pear.php.net/package/Mime_Type',
122 'include'=>'MIME/Type.php',
123 'check_class'=>'Mime_Type'
126 'name'=>'Net_URL_Mapper',
127 'pear'=>'Net_URL_Mapper',
128 'url'=>'http://pear.php.net/package/Net_URL_Mapper',
129 'include'=>'Net/URL/Mapper.php',
130 'check_class'=>'Net_URL_Mapper'
135 'url'=>'http://pear.php.net/package/Net_LDAP2',
136 'deb'=>'php-net-ldap2',
137 'include'=>'Net/LDAP2.php',
138 'check_class'=>'Net_LDAP2'
141 'name'=>'Net_Socket',
142 'pear'=>'Net_Socket',
143 'url'=>'http://pear.php.net/package/Net_Socket',
144 'deb'=>'php-net-socket',
145 'include'=>'Net/Socket.php',
146 'check_class'=>'Net_Socket'
151 'url'=>'http://pear.php.net/package/Net_SMTP',
152 'deb'=>'php-net-smtp',
153 'include'=>'Net/SMTP.php',
154 'check_class'=>'Net_SMTP'
159 'url'=>'http://pear.php.net/package/Net_URL',
160 'deb'=>'php-net-url',
161 'include'=>'Net/URL.php',
162 'check_class'=>'Net_URL'
167 'url'=>'http://pear.php.net/package/Net_URL2',
168 'include'=>'Net/URL2.php',
169 'check_class'=>'Net_URL2'
172 'name'=>'Services_oEmbed',
173 'pear'=>'Services_oEmbed',
174 'url'=>'http://pear.php.net/package/Services_oEmbed',
175 'include'=>'Services/oEmbed.php',
176 'check_class'=>'Services_oEmbed'
180 'url'=>'http://stomp.codehaus.org/PHP',
181 'include'=>'Stomp.php',
182 'check_class'=>'Stomp'
185 'name'=>'System_Command',
186 'pear'=>'System_Command',
187 'url'=>'http://pear.php.net/package/System_Command',
188 'include'=>'System/Command.php',
189 'check_class'=>'System_Command'
193 'url'=>'http://code.google.com/p/xmpphp',
194 'include'=>'XMPPHP/XMPP.php',
195 'check_class'=>'XMPPHP_XMPP'
198 'name'=>'PHP Markdown',
199 'url'=>'http://www.michelf.com/projects/php-markdown/',
200 'include'=>'markdown.php',
201 'check_class'=>'Markdown_Parser'
205 'url'=>'http://code.google.com/p/oauth-php',
206 'include'=>'OAuth.php',
207 'check_class'=>'OAuthRequest'
212 'url'=>'http://pear.php.net/package/Validate',
213 'include'=>'Validate.php',
214 'check_class'=>'Validate'
220 'check_module' => 'mysql', // mysqli?
221 'installer' => 'mysql_db_installer',
224 'name' => 'PostgreSQL',
225 'check_module' => 'pgsql',
226 'installer' => 'pgsql_db_installer',
231 * the actual installation.
232 * If call libraries are present, then install
238 if (!checkPrereqs()) {
242 if (!empty($_GET['checklibs'])) {
245 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
254 * checks if an external libary is present
256 * @param string $external_library Name of library
258 * @return boolean indicates if library present
260 function haveExternalLibrary($external_library)
262 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
265 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
268 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
274 // Attempt to include a PHP file and report if it worked, while
275 // suppressing the annoying warning messages on failure.
276 function haveIncludeFile($filename) {
277 $old = error_reporting(error_reporting() & ~E_WARNING);
278 $ok = include_once($filename);
279 error_reporting($old);
284 * Check if all is ready for installation
288 function checkPrereqs()
292 if (file_exists(INSTALLDIR.'/config.php')) {
293 printf('<p class="error">Config file "config.php" already exists.</p>');
297 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
298 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
302 $reqs = array('gd', 'curl',
303 'xmlwriter', 'mbstring','tidy');
305 foreach ($reqs as $req) {
306 if (!checkExtension($req)) {
307 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
311 // Make sure we have at least one database module available
313 $missingExtensions = array();
314 foreach ($dbModules as $type => $info) {
315 if (!checkExtension($info['check_module'])) {
316 $missingExtensions[] = $info['check_module'];
320 if (count($missingExtensions) == count($dbModules)) {
321 $req = implode(', ', $missingExtensions);
322 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
326 if (!is_writable(INSTALLDIR)) {
327 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
328 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
332 // Check the subdirs used for file uploads
333 $fileSubdirs = array('avatar', 'background', 'file');
334 foreach ($fileSubdirs as $fileSubdir) {
335 $fileFullPath = INSTALLDIR."/$fileSubdir/";
336 if (!is_writable($fileFullPath)) {
337 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
338 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
347 * Checks if a php extension is both installed and loaded
349 * @param string $name of extension to check
351 * @return boolean whether extension is installed and loaded
353 function checkExtension($name)
355 if (extension_loaded($name)) {
357 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
358 // dl will throw a fatal error if it's disabled or we're in safe mode.
359 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
360 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
361 if (PHP_SHLIB_SUFFIX == 'dll') {
362 $soname = "php_" . $soname;
371 * Show list of libraries
377 global $external_libraries;
378 $present_libraries=array();
379 $absent_libraries=array();
380 foreach ($external_libraries as $external_library) {
381 if (haveExternalLibrary($external_library)) {
382 $present_libraries[]=$external_library;
384 $absent_libraries[]=$external_library;
388 <div class="instructions">
389 <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
390 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
391 <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>
392 <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>
393 <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>
395 <h2>Absent Libraries</h2>
396 <ul id="absent_libraries">
398 foreach ($absent_libraries as $library) {
400 if (isset($library['url'])) {
401 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
403 echo htmlentities($library['name']);
406 if (isset($library['deb'])) {
407 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
409 if (isset($library['rpm'])) {
410 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
412 if (isset($library['pear'])) {
413 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
419 <h2>Installed Libraries</h2>
420 <ul id="present_libraries">
422 foreach ($present_libraries as $library) {
424 if (isset($library['url'])) {
425 echo '<a href="'.$library['url'].'">'.htmlentities($library['name']).'</a>';
427 echo htmlentities($library['name']);
440 $checked = 'checked="checked" '; // Check the first one which exists
441 foreach ($dbModules as $type => $info) {
442 if (checkExtension($info['check_module'])) {
443 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
451 <dl id="page_notice" class="system_notice">
454 <div class="instructions">
455 <p>Enter your database connection information below to initialize the database.</p>
456 <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>
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 <label for="snapshot">Send stats to StatusNet Inc?</label>
504 <input type="checkbox" id="snapshot" name="snapshot" checked />
505 <p class="form_guide">Periodically send information about your site to StatusNet Inc</p>
508 <input type="submit" name="submit" class="submit" value="Submit" />
515 function updateStatus($status, $error=false)
517 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
520 function handlePost()
522 $host = $_POST['host'];
523 $dbtype = $_POST['dbtype'];
524 $database = $_POST['database'];
525 $username = $_POST['username'];
526 $password = $_POST['password'];
527 $sitename = $_POST['sitename'];
528 $snapshot = $_POST['snapshot'];
529 $fancy = !empty($_POST['fancy']);
530 $server = $_SERVER['HTTP_HOST'];
531 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
534 <dl class="system_notice">
542 updateStatus("No hostname specified.", true);
546 if (empty($database)) {
547 updateStatus("No database specified.", true);
551 if (empty($username)) {
552 updateStatus("No username specified.", true);
556 if (empty($sitename)) {
557 updateStatus("No sitename specified.", true);
567 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
570 // database connection failed, do not move on to create config file.
574 updateStatus("Writing config file...");
575 $res = writeConf($sitename, $server, $path, $fancy, $db, $snapshot);
578 updateStatus("Can't write config file.", true);
584 TODO https needs to be considered
586 $link = "http://".$server.'/'.$path;
588 updateStatus("StatusNet has been installed at $link");
589 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
592 function Pgsql_Db_installer($host, $database, $username, $password)
594 $connstring = "dbname=$database host=$host user=$username";
596 //No password would mean trust authentication used.
597 if (!empty($password)) {
598 $connstring .= " password=$password";
600 updateStatus("Starting installation...");
601 updateStatus("Checking database...");
602 $conn = pg_connect($connstring);
604 if ($conn ===false) {
605 updateStatus("Failed to connect to database: $connstring");
610 //ensure database encoding is UTF8
611 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
612 if ($record->server_encoding != 'UTF8') {
613 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
618 updateStatus("Running database script...");
619 //wrap in transaction;
620 pg_query($conn, 'BEGIN');
621 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
623 if ($res === false) {
624 updateStatus("Can't run database script.", true);
628 foreach (array('sms_carrier' => 'SMS carrier',
629 'notice_source' => 'notice source',
630 'foreign_services' => 'foreign service')
632 updateStatus(sprintf("Adding %s data to database...", $name));
633 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
634 if ($res === false) {
635 updateStatus(sprintf("Can't run %d script.", $name), true);
640 pg_query($conn, 'COMMIT');
642 if (empty($password)) {
643 $sqlUrl = "pgsql://$username@$host/$database";
645 $sqlUrl = "pgsql://$username:$password@$host/$database";
648 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
653 function Mysql_Db_installer($host, $database, $username, $password)
655 updateStatus("Starting installation...");
656 updateStatus("Checking database...");
658 $conn = mysql_connect($host, $username, $password);
660 updateStatus("Can't connect to server '$host' as '$username'.", true);
664 updateStatus("Changing to database...");
665 $res = mysql_select_db($database, $conn);
667 updateStatus("Can't change to database.", true);
671 updateStatus("Running database script...");
672 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
673 if ($res === false) {
674 updateStatus("Can't run database script.", true);
678 foreach (array('sms_carrier' => 'SMS carrier',
679 'notice_source' => 'notice source',
680 'foreign_services' => 'foreign service')
682 updateStatus(sprintf("Adding %s data to database...", $name));
683 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
684 if ($res === false) {
685 updateStatus(sprintf("Can't run %d script.", $name), true);
691 $sqlUrl = "mysqli://$username:$password@$host/$database";
692 $db = array('type' => 'mysql', 'database' => $sqlUrl);
696 function writeConf($sitename, $server, $path, $fancy, $db, $snapshot)
698 // assemble configuration file in a string
700 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
703 "\$config['site']['name'] = '$sitename';\n\n".
706 "\$config['site']['server'] = '$server';\n".
707 "\$config['site']['path'] = '$path'; \n\n".
709 // checks if fancy URLs are enabled
710 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
712 // send site stats to SNI
713 ($snapshot ? "\$config['snapshot']['run'] = 'web';\n\n":'').
716 "\$config['db']['database'] = '{$db['database']}';\n\n".
717 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
718 "\$config['db']['type'] = '{$db['type']}';\n\n";
719 // write configuration file out to install directory
720 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
726 * Install schema into the database
728 * @param string $filename location of database schema file
729 * @param dbconn $conn connection to database
730 * @param string $type type of database, currently mysql or pgsql
732 * @return boolean - indicating success or failure
734 function runDbScript($filename, $conn, $type = 'mysqli')
736 $sql = trim(file_get_contents($filename));
737 $stmts = explode(';', $sql);
738 foreach ($stmts as $stmt) {
740 if (!mb_strlen($stmt)) {
743 // FIXME: use PEAR::DB or PDO instead of our own switch
746 $res = mysql_query($stmt, $conn);
747 if ($res === false) {
748 $error = mysql_error();
752 $res = pg_query($conn, $stmt);
753 if ($res === false) {
754 $error = pg_last_error();
758 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
760 if ($res === false) {
761 updateStatus("ERROR ($error) for SQL '$stmt'");
769 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
771 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
772 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
773 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
775 <title>Install StatusNet</title>
776 <link rel="shortcut icon" href="favicon.ico"/>
777 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
778 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
779 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
780 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
781 <script src="js/jquery.min.js"></script>
782 <script src="js/install.js"></script>
787 <address id="site_contact" class="vcard">
788 <a class="url home bookmark" href=".">
789 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
790 <span class="fn org">StatusNet</span>
796 <h1>Install StatusNet</h1>