<?php
/*******************************************************************************
 *
 * LEIDEN OPEN VARIATION DATABASE (LOVD)
 *
 * Created     : 2003-08-18
 * Modified    : 2009-06-11
 * For LOVD    : 2.0-21
 *
 * Access      : Public
 * Purpose     : Initiate LOVD; verify config.ini, set system variables,
 *               configure environment, initiate database connections.
 *
 * Copyright   : 2004-2009 Leiden University Medical Center; http://www.LUMC.nl/
 * Programmer  : Ing. Ivo F.A.C. Fokkema <I.F.A.C.Fokkema@LUMC.nl>
 * Last edited : Ing. Ivo F.A.C. Fokkema <I.F.A.C.Fokkema@LUMC.nl>
 *
 *
 * This file is part of LOVD.
 *
 * LOVD is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * LOVD is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LOVD; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *************/

// Don't allow direct access.
if (!defined('ROOT_PATH')) {
    exit;
}

// 2008-08-11; 2.0-10 release update; Disable PathInfo because it allows XSS attacks through $_SERVER['PHP_SELF'].
if (!empty($_SERVER['PATH_INFO'])) {
    // Manipulating LOVD through crafted URL...
    header('HTTP/1.0 404 Not Found');
    die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' . "\n" .
        '<HTML><HEAD>' . "\n" .
        '<TITLE>404 Not Found</TITLE>' . "\n" .
        '</HEAD><BODY>' . "\n" .
        '<H1>Not Found</H1>' . "\n" .
        'The requested URL ' . substr($_SERVER['REQUEST_URI'] . '?', 0, -1 * (strlen(strstr($_SERVER['REQUEST_URI'] . '?', '?')))) . ' was not found on this server.<P>' . "\n" .
        '</BODY></HTML>');
}

// Require library standard functions.
require ROOT_PATH . 'inc-lib-init.php';

// Define module path.
define('MODULE_PATH', ROOT_PATH . 'modules/');

// Set error_reporting if necessary. We don't want notices to show. This will do
// fine most of the time.
if (ini_get('error_reporting') == E_ALL) {
    error_reporting(E_ALL ^ E_NOTICE);
}





// Initiate $_SETT array.
// 2008-02-27; 2.0-04; Known whether or not we're using SSL.
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && !empty($_SERVER['SSL_PROTOCOL'])) {
    // We're using SSL!
    define('SSL', true);
    define('SSL_PROTOCOL', $_SERVER['SSL_PROTOCOL']);
    define('PROTOCOL', 'https://');
} else {
    define('SSL', false);
    define('SSL_PROTOCOL', '');
    define('PROTOCOL', 'http://');
}

define('LEVEL_CURATOR', 1);
define('LEVEL_MANAGER', 5);
define('LEVEL_ADMIN', 9);

define('STATUS_NEW', 1);
define('STATUS_HIDDEN', 4);
define('STATUS_MARKED', 7);
define('STATUS_OK', 9);

$aRequired =
         array(
                'PHP' => '4.2.0',
                'MySQL' => '3.23.33',
              );

$_SETT = array(
                'system' =>
                     array(
                            'version' => '2.0-21',
                          ),
                'user_levels' =>
                     array(
                            LEVEL_ADMIN => 'Database administrator',
                            LEVEL_MANAGER => 'LOVD manager',
                            LEVEL_CURATOR => 'Curator',
                          ),
                'var_allele' =>
                     array(
                            0 => 'Unknown',
                            11 => 'Paternal (confirmed)',
                            10 => 'Paternal (inferred)',
                            21 => 'Maternal (confirmed)',
                            20 => 'Maternal (inferred)',
                            1 => 'Parent #1',
                            2 => 'Parent #2',
                            3 => 'Both (homozygous)',
                          ),
                'var_pathogenic' =>
                     array(
                            5 => 'Unknown',
                            9 => 'Pathogenic',
                            7 => 'Probably pathogenic',
                            3 => 'Probably no pathogenicity',
                            1 => 'No known pathogenicity',
                          ),
                'var_pathogenic_short' =>
                     array(
                            '11' => '-/-',
                            '13' => '-/-?',
                            '15' => '-/?',
                            '17' => '-/+?',
                            '19' => '-/+',
                            '31' => '-?/-',
                            '33' => '-?/-?',
                            '35' => '-?/?',
                            '37' => '-?/+?',
                            '39' => '-?/+',
                            '51' => '?/-',
                            '53' => '?/-?',
                            '55' => '?/?',
                            '57' => '?/+?',
                            '59' => '?/+',
                            '71' => '+?/-',
                            '73' => '+?/-?',
                            '75' => '+?/?',
                            '77' => '+?/+?',
                            '79' => '+?/+',
                            '91' => '+/-',
                            '93' => '+/-?',
                            '95' => '+/?',
                            '97' => '+/+?',
                            '99' => '+/+',
                          ),
                'var_status' =>
                     array(
                            STATUS_NEW => 'Submitted',
                            STATUS_HIDDEN => 'Non public',
                            STATUS_MARKED => 'Marked',
                            STATUS_OK => 'Public',
                          ),
                'update_levels' =>
                     array(
                            1 => 'Optional',
                            4 => 'Common',
                            5 => 'Suggested',
                            7 => 'Recommended',
                            8 => '<SPAN style="color:red;">Important</SPAN>',
                            9 => '<SPAN style="color:red;"><B>Critical</B></SPAN>',
                          ),
                'upstream_URL' => 'http://www.LOVD.nl/',
                'wikiprofessional_iprange' => '131.174.88.0-255',
                'variant_listsizes' =>
                     array(
                            25,
                            50,
                            100,
                            250,
                            500,
                            1000,
                          ),
                'notes_align' =>
                     array(
                            -1 => 'left',
                            0 => 'center',
                            1 => 'right',
                          ),
              );

// Complete version info.
list($_SETT['system']['tree'], $_SETT['system']['build']) = explode('-', $_SETT['system']['version'], 2);
$_SETT['update_URL'] = $_SETT['upstream_URL'] . $_SETT['system']['tree'] . '/package_update.php';
$_SETT['check_location_URL'] = $_SETT['upstream_URL'] . $_SETT['system']['tree'] . '/check_location.php';



// We define CONFIG_URI as the location of the config file.
define('CONFIG_URI', ROOT_PATH . 'config.ini');

// Config file exists?
if (!file_exists(CONFIG_URI)) {
    lovd_displayError('Init', 'Can\'t find config.ini');
}

// Config file readable?
if (!is_readable(CONFIG_URI)) {
    lovd_displayError('Init', 'Can\'t read config.ini');
}

// Open config file.
if (!$aConfig = file(CONFIG_URI)) {
    lovd_displayError('Init', 'Can\'t open config.ini');
}



// Parse config file.
$_INI = array();

$sKey = '';
foreach ($aConfig as $nLine => $sLine) {
    // Go through the file line by line.
    $sLine = trim($sLine);

    // Empty line or comment.
    if (!$sLine || substr($sLine, 0, 1) == '#') {
        continue;
    }

    // New section.
    if (preg_match('/^\[([A-Z][A-Z_ ]+[A-Z])\]$/i', $sLine, $aRegs)) {
        $sKey = $aRegs[1];
        $_INI[$sKey] = array();
        continue;
    }

    // Setting.
    if (preg_match('/^([A-Z_]+) *=(.*)$/i', $sLine, $aRegs)) {
        list(,$sVar, $sVal) = $aRegs;
        $sVal = trim($sVal);

        if (!$sVal) {
            $sVal = false;
        }

        // Set value in array.
        if ($sKey) {
            $_INI[$sKey][$sVar] = $sVal;
        } else {
            $_INI[$sVar] = $sVal;
        }

    } else {
        // Couldn't parse value.
        lovd_displayError('Init', 'Error parsing config file at line ' . ($nLine + 1));
    }
}

// We now have the $_INI variable filled according to the file's contents.
// Check the settings' values to see if they are valid.
$aConfigValues =
         array(
                'database' =>
                         array(
                                'hostname' =>
                                         array(
                                                'required' => true,
                                                'default'  => 'localhost',
                                                // 2008-10-16; 2.0-13; Include hostname:port and :/path/to/socket values.
                                                'pattern'  => '/^([0-9a-z][-0-9a-z.]*[0-9a-z](:[0-9]+)?|:[-0-9a-z.\/]+)$/i',
                                              ),
                                'username' =>
                                         array(
                                                'required' => true,
                                              ),
                                'password' =>
                                         array(
                                                'required' => true,
                                              ),
                                'database' =>
                                         array(
                                                'required' => true,
                                              ),
                                'table_prefix' =>
                                         array(
                                                'required' => true,
                                                'default'  => 'lovd',
                                                'pattern'  => '/^[A-Z0-9_]+$/i',
                                              ),
                                'engine' =>
                                         array(
                                                'required' => true,
                                                'default'  => 'InnoDB',
                                                'values'   => array('myisam' => 'MyISAM', 'innodb' => 'InnoDB'),
                                              ),
                              ),
              );

foreach ($aConfigValues as $sSection => $aVars) {
    foreach ($aVars as $sVar => $aVar) {
        if (!isset($_INI[$sSection][$sVar]) || !$_INI[$sSection][$sVar]) {
            // Nothing filled in...

            if (isset($aVar['default']) && $aVar['default']) {
                // Set default value.
                $_INI[$sSection][$sVar] = $aVar['default'];
            } elseif (isset($aVar['required']) && $aVar['required']) {
                // No default value, required setting not filled in.
                lovd_displayError('Init', 'Error parsing config file: missing required value for setting \'' . $sVar . '\' in section [' . $sSection . ']');
            }

        } else {
            // Value is present in $_INI.
            if (isset($aVar['pattern']) && !preg_match($aVar['pattern'], $_INI[$sSection][$sVar])) {
                // Error: a pattern is available, but it doesn't match the input!
                lovd_displayError('Init', 'Error parsing config file: incorrect value for setting \'' . $sVar . '\' in section [' . $sSection . ']');

            } elseif (isset($aVar['values']) && is_array($aVar['values'])) {
                // Value must be present in list of possible values.
                $_INI[$sSection][$sVar] = strtolower($_INI[$sSection][$sVar]);
                if (!array_key_exists($_INI[$sSection][$sVar], $aVar['values'])) {
                    // Error: a value list is available, but it doesn't match the input!
                    lovd_displayError('Init', 'Error parsing config file: incorrect value for setting \'' . $sVar . '\' in section [' . $sSection . ']');
                } else {
                    // Get correct value loaded.
                    $_INI[$sSection][$sVar] = $aVar['values'][$_INI[$sSection][$sVar]];
                }
            }
        }
    }
}





// Define table names (system-wide).
define('TABLEPREFIX', $_INI['database']['table_prefix']);
$_TABLES =
         array(
                'TABLE_USERS'         => TABLEPREFIX . '_users',
                'TABLE_SUBS'          => TABLEPREFIX . '_submitters',
                'TABLE_CONFIG'        => TABLEPREFIX . '_config',
                'TABLE_STATUS'        => TABLEPREFIX . '_status',
                'TABLE_COLS'          => TABLEPREFIX . '_columns',
                'TABLE_LINKS'         => TABLEPREFIX . '_links',
                'TABLE_SOURCES'       => TABLEPREFIX . '_external_sources',
                'TABLE_COUNTRIES'     => TABLEPREFIX . '_countries',
                'TABLE_LOGS'          => TABLEPREFIX . '_logs',
                'TABLE_MODULES'       => TABLEPREFIX . '_modules',
                'TABLE_DBS'           => TABLEPREFIX . '_genes',
                'TABLE_PATIENTS'      => TABLEPREFIX . '_patients',
                'TABLE_PATIENTS_COLS' => TABLEPREFIX . '_patients_columns',
                'TABLE_CURATES'       => TABLEPREFIX . '_users2genes',
                'TABLE_COLS2LINKS'    => TABLEPREFIX . '_columns2links',
                'TABLE_PAT2VAR'       => TABLEPREFIX . '_patients2variants',
                'TABLE_VAR_STATUS'    => TABLEPREFIX . '_variant_status',
                'TABLE_PATHOGENIC'    => TABLEPREFIX . '_variant_pathogenicity',
              );

foreach ($_TABLES as $sConst => $sTable) {
    define($sConst, $sTable);
}

// 2007-07-27; 2.0-beta-06; If no MySQL Support has been installed, LOVD would die with an empty page. Prevent this.
if (!function_exists('mysql_connect')) {
    lovd_displayError('Init', 'This PHP installation does not have MySQL support installed. Without it, LOVD will not function. Please install MySQL support for PHP.');
}

// Initiate Database Connection.
$_DB = @mysql_connect($_INI['database']['hostname'], $_INI['database']['username'], $_INI['database']['password']);
if (!$_DB) {
    // No connection!
    lovd_displayError('Init', 'Error connecting to database - ' . mysql_error());
}

$bSelected = @mysql_select_db($_INI['database']['database']);
if (!$bSelected) {
    // Can't select database.
    lovd_displayError('Init', 'Error selecting database - ' . mysql_error());
}

// Transactional tables... we need to support InnoDB tables, if selected.
if ($_INI['database']['engine'] == 'InnoDB') {
    list(,$sInnoDB) = @mysql_fetch_row(mysql_query('SHOW VARIABLES LIKE "have\_innodb"'));
    if ($sInnoDB != 'YES') {
        $_INI['database']['engine'] = 'MyISAM';

    } else {
        // If using transactional tables; set autocommit to 1.
        @mysql_query('SET AUTOCOMMIT=1');
    }
}

// Read system-wide configuration from the database.
$_CONF = @mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_CONFIG));

// Read LOVD status from the database.
$_STAT = @mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_STATUS));

if (!is_array($_CONF) || !count($_CONF) || !is_array($_STAT) || !count($_STAT) || !isset($_STAT['version']) || !preg_match('/^([1-9]\.[0-9](\.[0-9])?)\-([0-9a-z-]{2,11})$/', $_STAT['version'], $aRegsVersion)) {
    // We couldn't get the installation's configuration or status. Are we properly installed, then?

    // Copying information that is required for the includes, but can't be read from the database.
    $_CONF['system_title'] = 'LOVD - Leiden Open Variation Database';
    $_STAT = array('tree' => $_SETT['system']['tree'], 'build' => $_SETT['system']['build']);

    // Are we installed properly?
    $aTables = array();
    $q = mysql_query('SHOW TABLES LIKE "' . TABLEPREFIX . '\_%"');
    while ($r = mysql_fetch_row($q)) {
        if (in_array($r[0], $_TABLES)) {
            $aTables[] = $r[0];
        }
    }
    if (count($aTables) < count($_TABLES)) {
        // We're not completely installed.
        define('_NOT_INSTALLED_', true);
    }

    // FIXME; wondering if we could do this using (dirname($_SERVER['PHP_SELF']) == $installpath . 'install/') or similar.
    // 2009-03-09; 2.0-17; Added inc-js-database_url.php check because it gets included in the install directory.
    if (dirname(lovd_getProjectFile()) != '/install' && lovd_getProjectFile() != '/inc-js-database_url.php') {
        // We're not installing, so throwing an error.

        if (defined('_NOT_INSTALLED_')) {
            // We're not completely installed.
            require ROOT_PATH . 'install/inc-top.php';
            print('      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LOVD was not installed yet. Please <A href="' . ROOT_PATH . 'install/">install</A> LOVD first.<BR>' . "\n");
            require ROOT_PATH . 'inc-bot.php';
            exit;

        } else {
            // Can't get the configuration for unknown reason. Bail out.
            lovd_displayError('Init', 'Error retrieving LOVD configuration or status information');
        }
    } // This should leave us alone if we're installing, even if we've got all tables, but are not quite done yet.

} else {
    // Store additional version information.
    list(, $_STAT['tree'],, $_STAT['build']) = $aRegsVersion;
}

// 2008-09-22; 2.0-12; Prevent some troubles with the menu when the URL contains double slashes.
$_SERVER['PHP_SELF'] = lovd_cleanDirName($_SERVER['PHP_SELF']);

// 2008-02-28; 2.0-04; Use of SSL required?
// 2009-04-01; 2.0-17; SSL not required when exporting data to WikiProfessional because their scripts do not support it.
if (!empty($_CONF['use_ssl']) && !SSL && !(lovd_getProjectFile() == '/export_data.php' && !empty($_GET['format']) && $_GET['format'] == 'wiki')) {
    // We were enabled, when SSL was available. So I guess SSL is still available. If not, this line here would be a problem.
    // No, not sending any $_POST values either. Let's just assume no-one is working with LOVD when the ssl setting is activated.
    header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . ($_SERVER['QUERY_STRING']? '?' . $_SERVER['QUERY_STRING'] : ''));
    exit;
}



// Force GPC magic quoting.
if (!get_magic_quotes_gpc()) {
    lovd_magicQuoteAll();
}

// Start sessions - do we need to use cookies?
if (isset($_CONF['use_cookies']) && !$_CONF['use_cookies']) {
    ini_set('session.use_cookies', 0);
    ini_set('session.use_only_cookies', 0);
} else {
    ini_set('session.use_cookies', 1);
    ini_set('session.use_only_cookies', 1);
    // 'Undo' the session.use_trans_sid setting.
    ini_set('url_rewriter.tags', '');
    if (ini_get('session.cookie_path') == '/') {
        // 2007-06-15; 2.0-beta-04; stop sharing cookies with other systems - set the cookie path!
        ini_set('session.cookie_path', lovd_cleanDirName(dirname($_SERVER['PHP_SELF']) . '/' . ROOT_PATH));
    }
}
// 2008-09-04; 2.0-12; Be done with this stupid annoying cookie problem. Set the session name to something unique.
$_SETT['cookie_id'] = preg_replace('/[^A-Z0-9]+/i', '', $_SERVER['HTTP_HOST'] . lovd_cleanDirName(dirname($_SERVER['PHP_SELF']) . '/' . ROOT_PATH));
session_name('PHPSESSID_' . $_SETT['cookie_id']);

session_start();

// Load session data.
require ROOT_PATH . 'inc-auth.php';

// Often required to indicate the requested action.
if (!isset($_GET['action'])) {
    $_GET['action'] = '';
}

// 2008-11-21; 2.0-14; Force user to change password.
if ($_AUTH && !empty($_SESSION['password_force_change']) && lovd_getProjectFile() != '/account_update.php') {
    header('Location: ' . PROTOCOL . $_SERVER['HTTP_HOST'] . lovd_cleanDirName(dirname($_SERVER['PHP_SELF']) . '/' . ROOT_PATH) . 'account_update.php?action=change_passwd' . lovd_showSID(true, true));
    exit;
}



// The following applies only if the system is fully installed.
if (!defined('_NOT_INSTALLED_')) {
    // 2008-02-28; 2.0-04; We really don't need any of this, if we're loaded by the update picture.
    if (lovd_getProjectFile() != '/check_update.php') {
        // Load DB admin data; needed by sending messages.
        if (HAS_AUTH && $_AUTH['level'] == LEVEL_ADMIN) {
            // Saves me quering the database!
            $_SETT['admin'] = array('name' => $_AUTH['name'], 'email' => $_AUTH['email']);
        } else {
            $_SETT['admin'] = array();
            list($_SETT['admin']['name'], $_SETT['admin']['email']) = mysql_fetch_row(mysql_query('SELECT name, email FROM ' . TABLE_USERS . ' WHERE level = "' . LEVEL_ADMIN . '"'));
        }

        // Switch gene.
        if (isset($_GET['select_db']) && !(isset($_SESSION['currdb']) && $_SESSION['currdb'] == $_GET['select_db'])) {
            if (in_array($_GET['select_db'], lovd_getGeneList())) {
                // Change this in de database if user is logged in.
                if (HAS_AUTH && $_AUTH['current_db'] != $_GET['select_db']) {
                    $sQ = 'UPDATE ' . TABLE_USERS . ' SET current_db = "' . $_GET['select_db'] . '" WHERE userid = "' . $_AUTH['userid'] . '"';
                    $q = @mysql_query($sQ);
                    if (!$q) {
                        lovd_dbFout('UpdateCurrDB', $sQ, mysql_error(), false);
                    }
                }
                $_SESSION['currdb'] = $_GET['select_db'];
            } else {
                lovd_displayError('Init', 'You provided a non-existing gene database name');
            }
        }

        // Load gene data.
        if (isset($_SESSION['currdb']) && $_SESSION['currdb']) {
            $_SETT['currdb'] = @mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_DBS . ' WHERE symbol = "' . $_SESSION['currdb'] . '"'));
            if ($_SETT['currdb']) {
                $_SESSION['currsymb'] = substr($_SESSION['currdb'], 0, strpos($_SESSION['currdb'] . '_', '_'));
                define('TABLE_CURRDB_COLS', TABLEPREFIX . '_' . $_SESSION['currdb'] . '_columns');
                define('TABLE_CURRDB_VARS', TABLEPREFIX . '_' . $_SESSION['currdb'] . '_variants');
            } else {
                $_SESSION['currdb'] = false;
            }
        } else {
            $_SESSION['currdb'] = false;
        }

        // Just often used...
        define('GENE_COUNT', count(lovd_getGeneList()));

        // Simply so that we can build somewhat correct email headers.
        if (empty($_CONF['location_name'])) {
            $_CONF['location_name'] = $_SERVER['HTTP_HOST'];
        }
        if (empty($_CONF['email_address'])) {
            $_CONF['email_address'] = 'noreply@' . (substr($_SERVER['HTTP_HOST'], 0, 4) == 'www.'? substr($_SERVER['HTTP_HOST'], 4) : $_SERVER['HTTP_HOST']);
        }

        // Determine email header line endings.
        // 2009-02-27; 2.0-16; Define constant to quickly check if we're on Windows, since sending emails on Windows requires yet one more adaptation.
        if (strtoupper(substr(PHP_OS, 0, 3) == 'WIN')) {
                $sEol = "\r\n";
                define('ON_WINDOWS', true);
        } elseif (strtoupper(substr(PHP_OS, 0, 3) == 'MAC')) {
                $sEol = "\r";
                define('ON_WINDOWS', false);
        } else {
                $sEol = "\n";
                define('ON_WINDOWS', false);
        }

        $_SETT['headers'] = 'MIME-Version: 1.0' . $sEol .
                            'Content-Type: text/plain; charset=ISO-8859-1' . $sEol .
                            'X-Priority: 3' . $sEol .
                            'X-MSMail-Priority: Normal' . $sEol .
                            'X-Mailer: PHP/' . phpversion() . $sEol .
                            'From: ' . (ON_WINDOWS? '' : '"LOVDv.' . $_SETT['system']['tree'] . ' (' . $_CONF['location_name'] . ')" ') . '<' . $_CONF['email_address'] . '>';
    }

    // Load LOVD modules!
    require ROOT_PATH . 'class/modules.php';
    $_MODULES = new Modules;
}
?>
