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'
99 'url'=>'http://pear.php.net/package/Mail',
101 'include'=>'Mail.php',
102 'check_class'=>'Mail'
105 'name'=>'Mail_mimeDecode',
106 'pear'=>'Mail_mimeDecode',
107 'url'=>'http://pear.php.net/package/Mail_mimeDecode',
108 'deb'=>'php-mail-mimedecode',
109 'include'=>'Mail/mimeDecode.php',
110 'check_class'=>'Mail_mimeDecode'
115 'url'=>'http://pear.php.net/package/Mime_Type',
116 'include'=>'MIME/Type.php',
117 'check_class'=>'Mime_Type'
120 'name'=>'Net_URL_Mapper',
121 'pear'=>'Net_URL_Mapper',
122 'url'=>'http://pear.php.net/package/Net_URL_Mapper',
123 'include'=>'Net/URL/Mapper.php',
124 'check_class'=>'Net_URL_Mapper'
127 'name'=>'Net_Socket',
128 'pear'=>'Net_Socket',
129 'url'=>'http://pear.php.net/package/Net_Socket',
130 'deb'=>'php-net-socket',
131 'include'=>'Net/Socket.php',
132 'check_class'=>'Net_Socket'
137 'url'=>'http://pear.php.net/package/Net_SMTP',
138 'deb'=>'php-net-smtp',
139 'include'=>'Net/SMTP.php',
140 'check_class'=>'Net_SMTP'
145 'url'=>'http://pear.php.net/package/Net_URL',
146 'deb'=>'php-net-url',
147 'include'=>'Net/URL.php',
148 'check_class'=>'Net_URL'
153 'url'=>'http://pear.php.net/package/Net_URL2',
154 'include'=>'Net/URL2.php',
155 'check_class'=>'Net_URL2'
158 'name'=>'Services_oEmbed',
159 'pear'=>'Services_oEmbed',
160 'url'=>'http://pear.php.net/package/Services_oEmbed',
161 'include'=>'Services/oEmbed.php',
162 'check_class'=>'Services_oEmbed'
166 'url'=>'http://stomp.codehaus.org/PHP',
167 'include'=>'Stomp.php',
168 'check_class'=>'Stomp'
171 'name'=>'System_Command',
172 'pear'=>'System_Command',
173 'url'=>'http://pear.php.net/package/System_Command',
174 'include'=>'System/Command.php',
175 'check_class'=>'System_Command'
179 'url'=>'http://code.google.com/p/xmpphp',
180 'include'=>'XMPPHP/XMPP.php',
181 'check_class'=>'XMPPHP_XMPP'
184 'name'=>'PHP Markdown',
185 'url'=>'http://www.michelf.com/projects/php-markdown/',
186 'include'=>'markdown.php',
187 'check_class'=>'Markdown_Parser'
191 'url'=>'http://code.google.com/p/oauth-php',
192 'include'=>'OAuth.php',
193 'check_class'=>'OAuthRequest'
198 'url'=>'http://pear.php.net/package/Validate',
199 'include'=>'Validate.php',
200 'check_class'=>'Validate'
206 'check_module' => 'mysql', // mysqli?
207 'installer' => 'mysql_db_installer',
210 'name' => 'PostgreSQL',
211 'check_module' => 'pgsql',
212 'installer' => 'pgsql_db_installer',
217 * the actual installation.
218 * If call libraries are present, then install
224 if (!checkPrereqs()) {
228 if (!empty($_GET['checklibs'])) {
231 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
240 * checks if an external libary is present
242 * @param string $external_library Name of library
244 * @return boolean indicates if library present
246 function haveExternalLibrary($external_library)
248 if (isset($external_library['include']) && !haveIncludeFile($external_library['include'])) {
251 if (isset($external_library['check_function']) && ! function_exists($external_library['check_function'])) {
254 if (isset($external_library['check_class']) && ! class_exists($external_library['check_class'])) {
260 // Attempt to include a PHP file and report if it worked, while
261 // suppressing the annoying warning messages on failure.
262 function haveIncludeFile($filename) {
263 $old = error_reporting(error_reporting() & ~E_WARNING);
264 $ok = include_once($filename);
265 error_reporting($old);
270 * Check if all is ready for installation
274 function checkPrereqs()
278 if (file_exists(INSTALLDIR.'/config.php')) {
279 printf('<p class="error">Config file "config.php" already exists.</p>');
283 if (version_compare(PHP_VERSION, '5.2.3', '<')) {
284 printf('<p class="error">Require PHP version 5.2.3 or greater.</p>');
288 $reqs = array('gd', 'curl',
289 'xmlwriter', 'mbstring','tidy');
291 foreach ($reqs as $req) {
292 if (!checkExtension($req)) {
293 printf('<p class="error">Cannot load required extension: <code>%s</code></p>', $req);
297 // Make sure we have at least one database module available
299 $missingExtensions = array();
300 foreach ($dbModules as $type => $info) {
301 if (!checkExtension($info['check_module'])) {
302 $missingExtensions[] = $info['check_module'];
306 if (count($missingExtensions) == count($dbModules)) {
307 $req = implode(', ', $missingExtensions);
308 printf('<p class="error">Cannot find mysql or pgsql extension. You need one or the other.');
312 if (!is_writable(INSTALLDIR)) {
313 printf('<p class="error">Cannot write config file to: <code>%s</code></p>', INSTALLDIR);
314 printf('<p>On your server, try this command: <code>chmod a+w %s</code>', INSTALLDIR);
318 // Check the subdirs used for file uploads
319 $fileSubdirs = array('avatar', 'background', 'file');
320 foreach ($fileSubdirs as $fileSubdir) {
321 $fileFullPath = INSTALLDIR."/$fileSubdir/";
322 if (!is_writable($fileFullPath)) {
323 printf('<p class="error">Cannot write to %s directory: <code>%s</code></p>', $fileSubdir, $fileFullPath);
324 printf('<p>On your server, try this command: <code>chmod a+w %s</code></p>', $fileFullPath);
333 * Checks if a php extension is both installed and loaded
335 * @param string $name of extension to check
337 * @return boolean whether extension is installed and loaded
339 function checkExtension($name)
341 if (extension_loaded($name)) {
343 } elseif (function_exists('dl') && ini_get('enable_dl') && !ini_get('safe_mode')) {
344 // dl will throw a fatal error if it's disabled or we're in safe mode.
345 // More fun, it may not even exist under some SAPIs in 5.3.0 or later...
346 $soname = $name . '.' . PHP_SHLIB_SUFFIX;
347 if (PHP_SHLIB_SUFFIX == 'dll') {
348 $soname = "php_" . $soname;
357 * Show list of libraries
363 global $external_libraries;
364 $present_libraries=array();
365 $absent_libraries=array();
366 foreach ($external_libraries as $external_library) {
367 if (haveExternalLibrary($external_library)) {
368 $present_libraries[]=$external_library;
370 $absent_libraries[]=$external_library;
374 <div class="instructions">
375 <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
376 libraries instead, as they tend to provide security updates faster, and may offer improved performance.</p>
377 <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>
378 <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>
379 <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>
381 <h2>Absent Libraries</h2>
382 <ul id="absent_libraries">
384 foreach ($absent_libraries as $library) {
386 if (isset($library['url'])) {
387 echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
389 echo htmlentities($library['name']);
392 if (isset($library['deb'])) {
393 echo '<li class="deb package">deb: <a href="apt:' . urlencode($library['deb']) . '">' . htmlentities($library['deb']) . '</a></li>';
395 if (isset($library['rpm'])) {
396 echo '<li class="rpm package">rpm: ' . htmlentities($library['rpm']) . '</li>';
398 if (isset($library['pear'])) {
399 echo '<li class="pear package">pear: ' . htmlentities($library['pear']) . '</li>';
405 <h2>Installed Libraries</h2>
406 <ul id="present_libraries">
408 foreach ($present_libraries as $library) {
410 if (isset($library['url'])) {
411 echo '<a href=">'.$library['url'].'">'.htmlentities($library['name']).'</a>';
413 echo htmlentities($library['name']);
426 $checked = 'checked="checked" '; // Check the first one which exists
427 foreach ($dbModules as $type => $info) {
428 if (checkExtension($info['check_module'])) {
429 $dbRadios .= "<input type=\"radio\" name=\"dbtype\" id=\"dbtype-$type\" value=\"$type\" $checked/> $info[name]<br />\n";
437 <dl id="page_notice" class="system_notice">
440 <div class="instructions">
441 <p>Enter your database connection information below to initialize the database.</p>
442 <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>
446 <form method="post" action="install.php" class="form_settings" id="form_install">
448 <legend>Connection settings</legend>
449 <ul class="form_data">
451 <label for="sitename">Site name</label>
452 <input type="text" id="sitename" name="sitename" />
453 <p class="form_guide">The name of your site</p>
456 <label for="fancy-enable">Fancy URLs</label>
457 <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
458 <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
459 <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
462 <label for="host">Hostname</label>
463 <input type="text" id="host" name="host" />
464 <p class="form_guide">Database hostname</p>
468 <label for="dbtype">Type</label>
470 <p class="form_guide">Database type</p>
474 <label for="database">Name</label>
475 <input type="text" id="database" name="database" />
476 <p class="form_guide">Database name</p>
479 <label for="username">Username</label>
480 <input type="text" id="username" name="username" />
481 <p class="form_guide">Database username</p>
484 <label for="password">Password</label>
485 <input type="password" id="password" name="password" />
486 <p class="form_guide">Database password (optional)</p>
489 <input type="submit" name="submit" class="submit" value="Submit" />
496 function updateStatus($status, $error=false)
498 echo '<li' . ($error ? ' class="error"': '' ) . ">$status</li>";
501 function handlePost()
503 $host = $_POST['host'];
504 $dbtype = $_POST['dbtype'];
505 $database = $_POST['database'];
506 $username = $_POST['username'];
507 $password = $_POST['password'];
508 $sitename = $_POST['sitename'];
509 $fancy = !empty($_POST['fancy']);
510 $server = $_SERVER['HTTP_HOST'];
511 $path = substr(dirname($_SERVER['PHP_SELF']), 1);
514 <dl class="system_notice">
522 updateStatus("No hostname specified.", true);
526 if (empty($database)) {
527 updateStatus("No database specified.", true);
531 if (empty($username)) {
532 updateStatus("No username specified.", true);
536 if (empty($sitename)) {
537 updateStatus("No sitename specified.", true);
547 $db = call_user_func($dbModules[$dbtype]['installer'], $host, $database, $username, $password);
550 // database connection failed, do not move on to create config file.
554 updateStatus("Writing config file...");
555 $res = writeConf($sitename, $server, $path, $fancy, $db);
558 updateStatus("Can't write config file.", true);
564 TODO https needs to be considered
566 $link = "http://".$server.'/'.$path;
568 updateStatus("StatusNet has been installed at $link");
569 updateStatus("You can visit your <a href='$link'>new StatusNet site</a>.");
572 function Pgsql_Db_installer($host, $database, $username, $password)
574 $connstring = "dbname=$database host=$host user=$username";
576 //No password would mean trust authentication used.
577 if (!empty($password)) {
578 $connstring .= " password=$password";
580 updateStatus("Starting installation...");
581 updateStatus("Checking database...");
582 $conn = pg_connect($connstring);
584 if ($conn ===false) {
585 updateStatus("Failed to connect to database: $connstring");
590 //ensure database encoding is UTF8
591 $record = pg_fetch_object(pg_query($conn, 'SHOW server_encoding'));
592 if ($record->server_encoding != 'UTF8') {
593 updateStatus("StatusNet requires UTF8 character encoding. Your database is ". htmlentities($record->server_encoding));
598 updateStatus("Running database script...");
599 //wrap in transaction;
600 pg_query($conn, 'BEGIN');
601 $res = runDbScript(INSTALLDIR.'/db/statusnet_pg.sql', $conn, 'pgsql');
603 if ($res === false) {
604 updateStatus("Can't run database script.", true);
608 foreach (array('sms_carrier' => 'SMS carrier',
609 'notice_source' => 'notice source',
610 'foreign_services' => 'foreign service')
612 updateStatus(sprintf("Adding %s data to database...", $name));
613 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn, 'pgsql');
614 if ($res === false) {
615 updateStatus(sprintf("Can't run %d script.", $name), true);
620 pg_query($conn, 'COMMIT');
622 if (empty($password)) {
623 $sqlUrl = "pgsql://$username@$host/$database";
625 $sqlUrl = "pgsql://$username:$password@$host/$database";
628 $db = array('type' => 'pgsql', 'database' => $sqlUrl);
633 function Mysql_Db_installer($host, $database, $username, $password)
635 updateStatus("Starting installation...");
636 updateStatus("Checking database...");
638 $conn = mysql_connect($host, $username, $password);
640 updateStatus("Can't connect to server '$host' as '$username'.", true);
644 updateStatus("Changing to database...");
645 $res = mysql_select_db($database, $conn);
647 updateStatus("Can't change to database.", true);
651 updateStatus("Running database script...");
652 $res = runDbScript(INSTALLDIR.'/db/statusnet.sql', $conn);
653 if ($res === false) {
654 updateStatus("Can't run database script.", true);
658 foreach (array('sms_carrier' => 'SMS carrier',
659 'notice_source' => 'notice source',
660 'foreign_services' => 'foreign service')
662 updateStatus(sprintf("Adding %s data to database...", $name));
663 $res = runDbScript(INSTALLDIR.'/db/'.$scr.'.sql', $conn);
664 if ($res === false) {
665 updateStatus(sprintf("Can't run %d script.", $name), true);
671 $sqlUrl = "mysqli://$username:$password@$host/$database";
672 $db = array('type' => 'mysql', 'database' => $sqlUrl);
676 function writeConf($sitename, $server, $path, $fancy, $db)
678 // assemble configuration file in a string
680 "if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }\n\n".
683 "\$config['site']['name'] = '$sitename';\n\n".
686 "\$config['site']['server'] = '$server';\n".
687 "\$config['site']['path'] = '$path'; \n\n".
689 // checks if fancy URLs are enabled
690 ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
693 "\$config['db']['database'] = '{$db['database']}';\n\n".
694 ($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
695 "\$config['db']['type'] = '{$db['type']}';\n\n".
698 // write configuration file out to install directory
699 $res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
705 * Install schema into the database
707 * @param string $filename location of database schema file
708 * @param dbconn $conn connection to database
709 * @param string $type type of database, currently mysql or pgsql
711 * @return boolean - indicating success or failure
713 function runDbScript($filename, $conn, $type = 'mysqli')
715 $sql = trim(file_get_contents($filename));
716 $stmts = explode(';', $sql);
717 foreach ($stmts as $stmt) {
719 if (!mb_strlen($stmt)) {
722 // FIXME: use PEAR::DB or PDO instead of our own switch
725 $res = mysql_query($stmt, $conn);
726 if ($res === false) {
727 $error = mysql_error();
731 $res = pg_query($conn, $stmt);
732 if ($res === false) {
733 $error = pg_last_error();
737 updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
739 if ($res === false) {
740 updateStatus("ERROR ($error) for SQL '$stmt'");
748 <?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
750 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
751 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
752 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
754 <title>Install StatusNet</title>
755 <link rel="shortcut icon" href="favicon.ico"/>
756 <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
757 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
758 <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
759 <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
760 <script src="js/jquery.min.js"></script>
761 <script src="js/install.js"></script>
766 <address id="site_contact" class="vcard">
767 <a class="url home bookmark" href=".">
768 <img class="logo photo" src="theme/default/logo.png" alt="StatusNet"/>
769 <span class="fn org">StatusNet</span>
775 <h1>Install StatusNet</h1>