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>
457 <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>
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">Username</label>
495 <input type="text" id="username" name="username" />
496 <p class="form_guide">Database username</p>
499 <label for="password">Password</label>
500 <input type="password" id="password" name="password" />
501 <p class="form_guide">Database password (optional)</p>
504 <input type="submit" name="submit" class="submit" value="Submit" />
511 function updateStatus($status, $error=false)
513 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
516 function handlePost()
518 $host = $_POST['host'];
519 $dbtype = $_POST['dbtype'];
520 $database = $_POST['database'];
521 $username = $_POST['username'];
522 $password = $_POST['password'];
523 $sitename = $_POST['sitename'];
524 $fancy = !empty($_POST['fancy']);
525 $server = $_SERVER['HTTP_HOST'];
526 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
529 <dl class="system_notice">
537 updateStatus("No hostname specified.", true);
541 if (empty($database)) {
542 updateStatus("No database specified.", true);
546 if (empty($username)) {
547 updateStatus("No username specified.", true);
551 if (empty($sitename)) {
552 updateStatus("No sitename specified.", true);
562 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
565 // database connection failed, do not move on to create config file.
569 updateStatus("Writing config file...");
570 $res = writeConf($sitename, $server, $path, $fancy, $db);
573 updateStatus("Can't write config file.", true);
579 TODO https needs to be considered
581 $link = "http://".$server.'/'.$path;
583 updateStatus("StatusNet has been installed at $link");
584 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
587 function Pgsql_Db_installer($host, $database, $username, $password)
589 $connstring = "dbname=$database host=$host user=$username";
591 //No password would mean trust authentication used.
592 if (!empty($password)) {
593 $connstring .= " password=$password";
595 updateStatus("Starting installation...");
596 updateStatus("Checking database...");
597 $conn = pg_connect($connstring);
599 if ($conn ===false) {
600 updateStatus("Failed to connect to database: $connstring");
605 //ensure database encoding is UTF8
606 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
607 if ($record->server_encoding != 'UTF8') {
608 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
613 updateStatus("Running database script...");
614 //wrap in transaction;
615 pg_query($conn, 'BEGIN');
616 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
618 if ($res === false) {
619 updateStatus("Can't run database script.", true);
623 foreach (array('sms_carrier' => 'SMS carrier',
624 'notice_source' => 'notice source',
625 'foreign_services' => 'foreign service')
627 updateStatus(sprintf("Adding %s data to database...", $name));
628 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
629 if ($res === false) {
630 updateStatus(sprintf("Can't run %d script.", $name), true);
635 pg_query($conn, 'COMMIT');
637 if (empty($password)) {
638 $sqlUrl = "pgsql://$username@$host/$database";
640 $sqlUrl = "pgsql://$username:$password@$host/$database";
643 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
648 function Mysql_Db_installer($host, $database, $username, $password)
650 updateStatus("Starting installation...");
651 updateStatus("Checking database...");
653 $conn = mysql_connect($host, $username, $password);
655 updateStatus("Can't connect to server '$host' as '$username'.", true);
659 updateStatus("Changing to database...");
660 $res = mysql_select_db($database, $conn);
662 updateStatus("Can't change to database.", true);
666 updateStatus("Running database script...");
667 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
668 if ($res === false) {
669 updateStatus("Can't run database script.", true);
673 foreach (array('sms_carrier' => 'SMS carrier',
674 'notice_source' => 'notice source',
675 'foreign_services' => 'foreign service')
677 updateStatus(sprintf("Adding %s data to database...", $name));
678 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
679 if ($res === false) {
680 updateStatus(sprintf("Can't run %d script.", $name), true);
686 $sqlUrl = "mysqli://$username:$password@$host/$database";
687 $db = array('type' => 'mysql', 'database' => $sqlUrl);
691 function writeConf($sitename, $server, $path, $fancy, $db)
693 // assemble configuration file in a string
695 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
698 "\$config['site']['name'] = '$sitename';\n\n".
701 "\$config['site']['server'] = '$server';\n".
702 "\$config['site']['path'] = '$path'; \n\n".
704 // checks if fancy URLs are enabled
705 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
708 "\$config['db']['database'] = '{$db['database']}';\n\n".
709 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
710 "\$config['db']['type'] = '{$db['type']}';\n\n";
711 // write configuration file out to install directory
712 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
718 * Install schema into the database
720 * @param string $filename location of database schema file
721 * @param dbconn $conn connection to database
722 * @param string $type type of database, currently mysql or pgsql
724 * @return boolean - indicating success or failure
726 function runDbScript($filename, $conn, $type = 'mysqli')
728 $sql = trim(file_get_contents($filename));
729 $stmts = explode(';', $sql);
730 foreach ($stmts as $stmt) {
732 if (!mb_strlen($stmt)) {
735 // FIXME: use PEAR::DB or PDO instead of our own switch
738 $res = mysql_query($stmt, $conn);
739 if ($res === false) {
740 $error = mysql_error();
744 $res = pg_query($conn, $stmt);
745 if ($res === false) {
746 $error = pg_last_error();
750 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
752 if ($res === false) {
753 updateStatus("ERROR ($error) for SQL '$stmt'");
761 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
763 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
764 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
765 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
767 <title>Install StatusNet</title>
768 <link rel="shortcut icon" href="favicon.ico"/>
769 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
770 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
771 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
772 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
773 <script src="js/jquery.min.js"></script>
774 <script src="js/install.js"></script>
779 <address id="site_contact" class="vcard">
780 <a class="url home bookmark" href=".">
781 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
782 <span class="fn org">StatusNet</span>
788 <h1>Install StatusNet</h1>