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 <helfire@Erics-MBP.local>
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'
98 'url'=>'http://pear.php.net/package/Mail',
100 'include'=>'Mail.php',
101 'check_class'=>'Mail'
104 'name'=>'Mail_mimeDecode',
105 'pear'=>'Mail_mimeDecode',
106 'url'=>'http://pear.php.net/package/Mail_mimeDecode',
107 'deb'=>'php-mail-mimedecode',
108 'include'=>'Mail/mimeDecode.php',
109 'check_class'=>'Mail_mimeDecode'
114 'url'=>'http://pear.php.net/package/Mime_Type',
115 'include'=>'MIME/Type.php',
116 'check_class'=>'Mime_Type'
119 'name'=>'Net_URL_Mapper',
120 'pear'=>'Net_URL_Mapper',
121 'url'=>'http://pear.php.net/package/Net_URL_Mapper',
122 'include'=>'Net/URL/Mapper.php',
123 'check_class'=>'Net_URL_Mapper'
126 'name'=>'Net_Socket',
127 'pear'=>'Net_Socket',
128 'url'=>'http://pear.php.net/package/Net_Socket',
129 'deb'=>'php-net-socket',
130 'include'=>'Net/Socket.php',
131 'check_class'=>'Net_Socket'
136 'url'=>'http://pear.php.net/package/Net_SMTP',
137 'deb'=>'php-net-smtp',
138 'include'=>'Net/SMTP.php',
139 'check_class'=>'Net_SMTP'
144 'url'=>'http://pear.php.net/package/Net_URL',
145 'deb'=>'php-net-url',
146 'include'=>'Net/URL.php',
147 'check_class'=>'Net_URL'
152 'url'=>'http://pear.php.net/package/Net_URL2',
153 'include'=>'Net/URL2.php',
154 'check_class'=>'Net_URL2'
157 'name'=>'Services_oEmbed',
158 'pear'=>'Services_oEmbed',
159 'url'=>'http://pear.php.net/package/Services_oEmbed',
160 'include'=>'Services/oEmbed.php',
161 'check_class'=>'Services_oEmbed'
165 'url'=>'http://stomp.codehaus.org/PHP',
166 'include'=>'Stomp.php',
167 'check_class'=>'Stomp'
170 'name'=>'System_Command',
171 'pear'=>'System_Command',
172 'url'=>'http://pear.php.net/package/System_Command',
173 'include'=>'System/Command.php',
174 'check_class'=>'System_Command'
178 'url'=>'http://code.google.com/p/xmpphp',
179 'include'=>'XMPPHP/XMPP.php',
180 'check_class'=>'XMPPHP_XMPP'
183 'name'=>'PHP Markdown',
184 'url'=>'http://www.michelf.com/projects/php-markdown/',
185 'include'=>'markdown.php',
186 'check_class'=>'Markdown_Parser'
190 'url'=>'http://code.google.com/p/oauth-php',
191 'include'=>'OAuth.php',
192 'check_class'=>'OAuthRequest'
197 'url'=>'http://pear.php.net/package/Validate',
198 'include'=>'Validate.php',
199 'check_class'=>'Validate'
205 'check_module' => 'mysql', // mysqli?
206 'installer' => 'mysql_db_installer',
209 'name' => 'PostgreSQL',
210 'check_module' => 'pgsql',
211 'installer' => 'pgsql_db_installer',
216 * the actual installation.
217 * If call libraries are present, then install
223 if (!checkPrereqs()) {
227 if (!empty($_GET['checklibs'])) {
230 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
239 * checks if an external libary is present
241 * @param string $external_library Name of library
243 * @return boolean indicates if library present
245 function haveExternalLibrary($external_library)
247 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
250 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
253 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
259 // Attempt to include a PHP file and report if it worked, while
260 // suppressing the annoying warning messages on failure.
261 function haveIncludeFile($filename) {
262 $old = error_reporting(error_reporting() & ~E_WARNING);
263 $ok = include_once($filename);
264 error_reporting($old);
269 * Check if all is ready for installation
273 function checkPrereqs()
277 if (file_exists(INSTALLDIR.'/config.php')) {
278 printf('<p class="error">Config file "config.php" already exists.</p>');
282 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
283 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
287 $reqs = array('gd', 'curl',
288 'xmlwriter', 'mbstring','tidy');
290 foreach ($reqs as $req) {
291 if (!checkExtension($req)) {
292 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
296 // Make sure we have at least one database module available
298 $missingExtensions = array();
299 foreach ($dbModules as $type => $info) {
300 if (!checkExtension($info['check_module'])) {
301 $missingExtensions[] = $info['check_module'];
305 if (count($missingExtensions) == count($dbModules)) {
306 $req = implode(', ', $missingExtensions);
307 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
311 if (!is_writable(INSTALLDIR)) {
312 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
313 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
317 // Check the subdirs used for file uploads
318 $fileSubdirs = array('avatar', 'background', 'file');
319 foreach ($fileSubdirs as $fileSubdir) {
320 $fileFullPath = INSTALLDIR."/$fileSubdir/";
321 if (!is_writable($fileFullPath)) {
322 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
323 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
332 * Checks if a php extension is both installed and loaded
334 * @param string $name of extension to check
336 * @return boolean whether extension is installed and loaded
338 function checkExtension($name)
340 if (extension_loaded($name)) {
342 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
343 // dl will throw a fatal error if it's disabled or we're in safe mode.
344 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
345 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
346 if (PHP_SHLIB_SUFFIX == 'dll') {
347 $soname = "php_" . $soname;
356 * Show list of libraries
362 global $external_libraries;
363 $present_libraries=array();
364 $absent_libraries=array();
365 foreach ($external_libraries as $external_library) {
366 if (haveExternalLibrary($external_library)) {
367 $present_libraries[]=$external_library;
369 $absent_libraries[]=$external_library;
373 <div class="instructions">
374 <p>Laconica 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
375 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
376 <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>
377 <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>
378 <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>
380 <h2>Absent Libraries</h2>
381 <ul id="absent_libraries">
383 foreach ($absent_libraries as $library) {
385 if (isset($library['url'])) {
386 echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
388 echo htmlentities($library['name']);
391 if (isset($library['deb'])) {
392 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
394 if (isset($library['rpm'])) {
395 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
397 if (isset($library['pear'])) {
398 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
404 <h2>Installed Libraries</h2>
405 <ul id="present_libraries">
407 foreach ($present_libraries as $library) {
409 if (isset($library['url'])) {
410 echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
412 echo htmlentities($library['name']);
425 $checked = 'checked="checked" '; // Check the first one which exists
426 foreach ($dbModules as $type => $info) {
427 if (checkExtension($info['check_module'])) {
428 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
436 <dl id="page_notice" class="system_notice">
439 <div class="instructions">
440 <p>Enter your database connection information below to initialize the database.</p>
441 <p>Laconica 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>
445 <form method="post" action="install.php" class="form_settings" id="form_install">
447 <legend>Connection settings</legend>
448 <ul class="form_data">
450 <label for="sitename">Site name</label>
451 <input type="text" id="sitename" name="sitename" />
452 <p class="form_guide">The name of your site</p>
455 <label for="fancy-enable">Fancy URLs</label>
456 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
457 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
458 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
461 <label for="host">Hostname</label>
462 <input type="text" id="host" name="host" />
463 <p class="form_guide">Database hostname</p>
467 <label for="dbtype">Type</label>
469 <p class="form_guide">Database type</p>
473 <label for="database">Name</label>
474 <input type="text" id="database" name="database" />
475 <p class="form_guide">Database name</p>
478 <label for="username">Username</label>
479 <input type="text" id="username" name="username" />
480 <p class="form_guide">Database username</p>
483 <label for="password">Password</label>
484 <input type="password" id="password" name="password" />
485 <p class="form_guide">Database password (optional)</p>
488 <input type="submit" name="submit" class="submit" value="Submit" />
495 function updateStatus($status, $error=false)
497 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
500 function handlePost()
502 $host = $_POST['host'];
503 $dbtype = $_POST['dbtype'];
504 $database = $_POST['database'];
505 $username = $_POST['username'];
506 $password = $_POST['password'];
507 $sitename = $_POST['sitename'];
508 $fancy = !empty($_POST['fancy']);
509 $server = $_SERVER['HTTP_HOST'];
510 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
513 <dl class="system_notice">
521 updateStatus("No hostname specified.", true);
525 if (empty($database)) {
526 updateStatus("No database specified.", true);
530 if (empty($username)) {
531 updateStatus("No username specified.", true);
535 if (empty($sitename)) {
536 updateStatus("No sitename specified.", true);
546 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
549 // database connection failed, do not move on to create config file.
553 updateStatus("Writing config file...");
554 $res = writeConf($sitename, $server, $path, $fancy, $db);
557 updateStatus("Can't write config file.", true);
563 TODO https needs to be considered
565 $link = "http://".$server.'/'.$path;
567 updateStatus("StatusNet has been installed at $link");
568 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
571 function Pgsql_Db_installer($host, $database, $username, $password)
573 $connstring = "dbname=$database host=$host user=$username";
575 //No password would mean trust authentication used.
576 if (!empty($password)) {
577 $connstring .= " password=$password";
579 updateStatus("Starting installation...");
580 updateStatus("Checking database...");
581 $conn = pg_connect($connstring);
583 if ($conn ===false) {
584 updateStatus("Failed to connect to database: $connstring");
589 //ensure database encoding is UTF8
590 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
591 if ($record->server_encoding != 'UTF8') {
592 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
597 updateStatus("Running database script...");
598 //wrap in transaction;
599 pg_query($conn, 'BEGIN');
600 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
602 if ($res === false) {
603 updateStatus("Can't run database script.", true);
607 foreach (array('sms_carrier' => 'SMS carrier',
608 'notice_source' => 'notice source',
609 'foreign_services' => 'foreign service')
611 updateStatus(sprintf("Adding %s data to database...", $name));
612 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
613 if ($res === false) {
614 updateStatus(sprintf("Can't run %d script.", $name), true);
619 pg_query($conn, 'COMMIT');
621 if (empty($password)) {
622 $sqlUrl = "pgsql://$username@$host/$database";
624 $sqlUrl = "pgsql://$username:$password@$host/$database";
627 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
632 function Mysql_Db_installer($host, $database, $username, $password)
634 updateStatus("Starting installation...");
635 updateStatus("Checking database...");
637 $conn = mysql_connect($host, $username, $password);
639 updateStatus("Can't connect to server '$host' as '$username'.", true);
643 updateStatus("Changing to database...");
644 $res = mysql_select_db($database, $conn);
646 updateStatus("Can't change to database.", true);
650 updateStatus("Running database script...");
651 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
652 if ($res === false) {
653 updateStatus("Can't run database script.", true);
657 foreach (array('sms_carrier' => 'SMS carrier',
658 'notice_source' => 'notice source',
659 'foreign_services' => 'foreign service')
661 updateStatus(sprintf("Adding %s data to database...", $name));
662 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
663 if ($res === false) {
664 updateStatus(sprintf("Can't run %d script.", $name), true);
670 $sqlUrl = "mysqli://$username:$password@$host/$database";
671 $db = array('type' => 'mysql', 'database' => $sqlUrl);
675 function writeConf($sitename, $server, $path, $fancy, $db)
677 // assemble configuration file in a string
679 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
682 "\$config['site']['name'] = '$sitename';\n\n".
685 "\$config['site']['server'] = '$server';\n".
686 "\$config['site']['path'] = '$path'; \n\n".
688 // checks if fancy URLs are enabled
689 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
692 "\$config['db']['database'] = '{$db['database']}';\n\n".
693 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
694 "\$config['db']['type'] = '{$db['type']}';\n\n".
697 // write configuration file out to install directory
698 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
704 * Install schema into the database
706 * @param string $filename location of database schema file
707 * @param dbconn $conn connection to database
708 * @param string $type type of database, currently mysql or pgsql
710 * @return boolean - indicating success or failure
712 function runDbScript($filename, $conn, $type = 'mysqli')
714 $sql = trim(file_get_contents($filename));
715 $stmts = explode(';', $sql);
716 foreach ($stmts as $stmt) {
718 if (!mb_strlen($stmt)) {
721 // FIXME: use PEAR::DB or PDO instead of our own switch
724 $res = mysql_query($stmt, $conn);
725 if ($res === false) {
726 $error = mysql_error();
730 $res = pg_query($conn, $stmt);
731 if ($res === false) {
732 $error = pg_last_error();
736 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
738 if ($res === false) {
739 updateStatus("ERROR ($error) for SQL '$stmt'");
747 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
749 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
750 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
751 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
753 <title>Install StatusNet</title>
754 <link rel="shortcut icon" href="favicon.ico"/>
755 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
756 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
757 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
758 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
759 <script src="js/jquery.min.js"></script>
760 <script src="js/install.js"></script>
765 <address id="site_contact" class="vcard">
766 <a class="url home bookmark" href=".">
767 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
768 <span class="fn org">StatusNet</span>
774 <h1>Install StatusNet</h1>