<?php
/*******************************************************************************
 *
 * LEIDEN OPEN VARIATION DATABASE (LOVD)
 *
 * Created     : 2007-03-27
 * Modified    : 2010-04-13
 * For LOVD    : 2.0-26
 *
 * Access      : Curators and up.
 * Purpose     : Import previously downloaded file into the database.
 *
 * Copyright   : 2004-2010 Leiden University Medical Center; http://www.LUMC.nl/
 * Programmers : Ing. Ivo F.A.C. Fokkema <I.F.A.C.Fokkema@LUMC.nl>
 *               Ir. Gerard C.P. Schaafsma <G.C.P.Schaafsma@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
 *
 *************/

define('ROOT_PATH', './');
require ROOT_PATH . 'inc-init.php';

if (HAS_AUTH) {
    // If authorized, check for updates.
    require ROOT_PATH . 'inc-upgrade.php';
}

require ROOT_PATH . 'inc-lib-form.php';

// Require curator clearance.
lovd_requireAUTH(LEVEL_CURATOR);

// If no gene selected, present the selection list.
if (!$_SESSION['currdb'] || $_GET['action'] == 'switch_db') {
    lovd_switchDB();
}

// Needs to be curator for THIS gene.
if (!lovd_isCurator($_SESSION['currdb'])) {
    // NOTE that this does not unset certain links in the top menu. Links are available.
    require ROOT_PATH . 'inc-top.php';
    lovd_showInfoTable((GENE_COUNT? 'You are not allowed access to ' . (GENE_COUNT > 1? 'this gene database' : 'the installed gene database') . '. Please contact your manager or the administrator to grant you access.' : 'There are currently no databases installed.'), 'stop');
    require ROOT_PATH . 'inc-bot.php';
    exit;
}





$nMaxSize = 2097152; // 2 MB.
// 2009-04-03; 2.0-17; Include PHP maximum file size, that may be smaller.
$nMaxPHP = ini_get('upload_max_filesize');
$nMaxPHP = substr($nMaxPHP, 0, -1) * pow(1024, 2);
if ($nMaxPHP < $nMaxSize) {
    $nMaxSize = $nMaxPHP;
}

if (isset($_GET['sent'])) {
    lovd_errorClean();

    // No file...
    if ($_FILES['upload_file']['error'] > 0 && $_FILES['upload_file']['error'] < 4) {
        lovd_errorAdd('There was a problem with the file transfer. Please try again. The file cannot be larger than ' . round($nMaxSize/pow(1024, 2), 1) . ' MB.');

    } else if ($_FILES['upload_file']['error'] == 4 || !$_FILES['upload_file']['size']) {
        lovd_errorAdd('Please select a file to upload.');

    } else if ($_FILES['upload_file']['size'] > $nMaxSize) {
        lovd_errorAdd('The file cannot be larger than ' . round($nMaxSize/pow(1024, 2), 1) . ' MB.');

    } elseif ($_FILES['upload_file']['error']) {
        // Various errors available from 4.3.0 or later.
        lovd_errorAdd('There was an unknown problem with receiving the file properly, possibly because of the current server settings. If the problem persists, contact the database administrator.');
    }
    
    // 2009-05-15; 2.0-19; Find out the MIME-type of the uploaded file
    if (function_exists('mime_content_type') && mime_content_type($_FILES['upload_file']['tmp_name']) != 'text/plain') {
        lovd_errorAdd('The upload file is not a tab-delimited text file and cannot be imported');
    }

    if (!lovd_error()) {
        $fInput = @fopen($_FILES['upload_file']['tmp_name'], 'r');
        if (!$fInput) {
            lovd_errorAdd('Cannot open file after it was received by the server.');
        }
    }



    if (!lovd_error()) {
        // Start reading out text file and gathering data.
        $nLine = 1;

        $aLOVDCols      = array('variant_created_date_', 'variant_edited_date_', 'patient_created_date_', 'patient_edited_date_', 'ID_sort_', 'ID_allele_', 'ID_pathogenic_', 'ID_status_', 'ID_variantid_', 'ID_patientid_', 'ID_submitterid_', 'ID_variant_created_by_', 'ID_variant_edited_by_', 'ID_patient_created_by_', 'ID_patient_edited_by_');
        $aColsMandatory = array('ID_patientid_');
        $aIgnoredColumns = array();

        // Gather column info from database.
        require ROOT_PATH . 'class/currdb.php';
        $_CURRDB = new CurrDB(true, $_SESSION['currdb']);
        $sMutationCol = $_CURRDB->getMutationCol(); // For lovd_sort() and DB-ID suggestion.

        // Read version information or column names from file.
        $sLine = @fgets($fInput, 4096);
        $sLine = rtrim($sLine, "\r\n");

        // 2008-12-23; 2.0-15; Check if the first line with LOVD-version etc. is present
        if (!preg_match('/^### LOVD-version ([0-9]{4}\-[0-9]{2}[a-z0-9]) ###/', ltrim($sLine, '"'), $aRegs)) {
            lovd_errorAdd('The first line of the imported file should contain the LOVD header! (### LOVD-version ... etc.) <A href="' . ROOT_PATH . 'docs/variant_and_patient_data/importing_text_files.php">More information</A>.');
        } else {
            // 2008-02-29; 2.0-04; Version number added to download file.
            // Version number.
            $sFormatVersion = $aRegs[1];

            // 2009-01-27; 2.0-15; Check for data type.
            if (preg_match('/^### LOVD-version [0-9a-z-]+ ### ([A-Za-z\/]+) ###/', ltrim($sLine, '"'), $aRegs)) {
                // We received a datatype. It should be Variants/Patients or Patients/Variants.
                $aFormat = explode('/', $aRegs[1]);
                sort($aFormat);
                if ($aFormat != array('Patients', 'Variants')) {
                    lovd_errorAdd('The header of this file indicates it contains "' . $aRegs[1] . '". Expected "Variants/Patients".');
                    if ($aRegs[1] == 'Columns') {
                        lovd_errorAdd('Maybe you meant to import your file <A href="' . ROOT_PATH . 'setup_columns_global_import.php">here</A>?');
                    }
                }
            }

            // Read the next line for the column names.
            $sLine = @fgets($fInput, 4096);
            $sLine = rtrim($sLine, "\r\n");
        }

        // Column handling.
        $aColumns = explode("\t", $sLine);
        foreach ($aColumns as $nCol => $sCol) {
            $sCol = trim($sCol, '"{ }');
            // Unknown cols? Ignore.
            if (!in_array($sCol, $aLOVDCols) && !$_CURRDB->colExists($sCol)) {
                $aIgnoredColumns[] = $sCol; // 2009-03-18; 2.0-18; by Gerard: store ignored columns in array
                unset($aColumns[$nCol]);
            } else {
                $aColumns[$nCol] = $sCol;
            }
        }

        // LOVD mandatory columns really *must* be available.
        foreach ($aColsMandatory as $sCol) {
            if (!in_array($sCol, $aColumns)) {
                lovd_errorAdd('Cannot import this file, as it lacks the crucial "' . $sCol . '" columnn.');
            }
        }

        // Mandatory cols missing?
        // Directly accessing $aColList, since the checkMandatory is not going to help me here.
        foreach ($_CURRDB->aColList as $sCol => $aCol) {
            if ($aCol['mandatory'] && !in_array($sCol, $aColumns)) {
                // Mandatory column is missing from the file.
                lovd_errorAdd('Bluntly refusing to import this file, as it lacks the mandatory "' . $sCol . '" (' . $aCol['head_column'] . ') column.');
            }
        }



        if (!lovd_error()) {
            // Loop lines.
            $nLine = 2; // Header = line 2.
            $nMaxErrors = 25;
            // 2008-09-15; 2.0-12; Added increased execution time to script to help import bigger files.
            if ((int) ini_get('max_execution_time') < 60) {
                set_time_limit(60);
            }

            // Save us a lot of queries, by saving this first.
            $aIDsUsers = array();
            $aIDsSubs = array();
            $qUsers = mysql_query('SELECT userid FROM ' . TABLE_USERS);
            while ($r = mysql_fetch_row($qUsers)) {
                $aIDsUsers[] = (int) $r[0];
            }
            // This one needs to be added, because 0 is a valid value for created_by and edited_by fields.
            $aIDsUsers[] = 0;
            $qSubs = mysql_query('SELECT submitterid FROM ' . TABLE_SUBS);
            while ($r = mysql_fetch_row($qSubs)) {
                $aIDsSubs[] = (int) $r[0];
            }
            // This one needs to be added, because 0 is a valid value for the submitterid field.
            $aIDsSubs[] = 0;

            // This can be a quite intensive process, but we need to check and verify all the data.
            $aVariants = array();
            $aPatients = array();
            $aPat2Var  = array();
            $nVariants = 0;
            $nPatients = 0;
            $nPat2Var  = 0;

            // 2008-02-12; 2.0-04
            // Fixed memory consumption problem of import script occuring in large databases when loading all variant data into the memory.
            // Full variant data will be loaded later on in this script, only when needed, one by one.
            // $qVar = mysql_query('SELECT * FROM ' . TABLE_CURRDB_VARS);
            $qVar = mysql_query('SELECT variantid FROM ' . TABLE_CURRDB_VARS);
            while ($z = mysql_fetch_assoc($qVar)) {
                $z['indb'] = true;
                $aVariants[(int) $z['variantid']] = $z;
            }

            // 2007-06-14; 2.0-beta-04
            // Fixed problem when importing new entries when already having entries in other gene databases.
            // $qPat = mysql_query('SELECT p.* FROM ' . TABLE_PATIENTS . ' AS p LEFT JOIN ' . TABLE_PAT2VAR . ' AS p2v USING (patientid) WHERE p2v.symbol = "' . $_SESSION['currdb'] . '"');
            // 2007-09-21; 2.0-beta-09
            // Fixed memory consumption problem of import script occuring in large databases when loading all patient data into the memory.
            // Full patient data will be loaded later on in this script, only when needed, one by one.
            // $qPat = mysql_query('SELECT * FROM ' . TABLE_PATIENTS);
            $qPat = mysql_query('SELECT patientid FROM ' . TABLE_PATIENTS);
            while ($z = mysql_fetch_assoc($qPat)) {
                $z['indb'] = true;
                $aPatients[(int) $z['patientid']] = $z;
            }

            // 2008-02-12; 2.0-04
            // Fixed memory consumption problem of import script occuring in large databases when loading all patient2variant data into the memory.
            // Full patient2variant data will be loaded later on in this script, only when needed, one by one.
            // $qPat2Var = mysql_query('SELECT * FROM ' . TABLE_PAT2VAR . ' WHERE symbol = "' . $_SESSION['currdb'] . '"');
            $qPat2Var = mysql_query('SELECT variantid, patientid, allele FROM ' . TABLE_PAT2VAR . ' WHERE symbol = "' . $_SESSION['currdb'] . '"');
            while ($z = mysql_fetch_assoc($qPat2Var)) {
                $z['indb'] = true;
                $aPat2Var[(int) $z['variantid'] . '|' . (int) $z['patientid'] . '|' . $z['allele']] = $z;
            }

            // 2008-02-29; 2.0-04; Changed the whole pathogenicity ID code list.
            $aTransformPathogenicity = array('0' => '1', '1' => '9', '9' => '5');

            // 2008-11-13; 2.0-14 added by Gerard
            // Initiate an array to keep track of assigned Variant/DBID numbers
            // use variants already seen in the upload file as keys when you add the number
            $aIDAssigned = array();
            // 2009-01-27; 2.0-15; Keep track of the maximum variantid in use.
            $nMaxVariantID = 0;



            // Read rest of the file.
            while (!feof($fInput) && $sLine = rtrim(fgets($fInput, 4096), "\r\n")) {
                $nLine ++;
                if (!trim($sLine)) {
                    // Empty line.
                    continue;
                }
                $aLine = explode("\t", $sLine);
                $aLineVar = array();
                $aLinePat = array();
                $aLinePat2Var = array();

                // Clean first.
                foreach ($aLine as $nKey => $sVal) {
                    // We need an extra loop, because we're going to access values of $aLine directly, and then all values should be cleaned.
                    if (substr($sVal, 0, 1) == '"' && substr($sVal, -1) == '"') {
                        $sVal = substr($sVal, 1, -1);
                    }

                    // 2009-07-14; 2.0-20; XSS attack prevention.
                    if (preg_match('/<.*>/', $sVal)) {
                        lovd_errorAdd('Disallowed tag found in column "' . $aColumns[$nKey] . '", on line ' . $nLine . '.');
                    }

                    // 2010-04-13; 2.0-26; Fix problem with getting quoted data in the database, by unquoting what we have here.
                    // Data from text files is not actually quoted, but when downloading data, LOVD quotes the data.
                    // LOVD was quoting database contents further on in this file to compare it with the imported data; of course this is removed.
                    // Since we're using mysql_real_escape_string() later, unquote the data from the text file!
                    $aLine[$nKey] = stripslashes($sVal);
                }

                foreach ($aLine as $nKey => $sVal) {
                    // Loop data, and verify it.
                    if (!isset($aColumns[$nKey])) {
                        // We're ignoring this column.
                        continue;
                    }
                    $sCol = $aColumns[$nKey];

                    // Check given ID's.
                    switch ($sCol) {
                        case 'variant_created_date_':
                            if ($sVal && !preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}(\:[0-9]{2})?)?$/', $sVal)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                            }
                            $aLinePat2Var['created_date'] = $sVal;
                            break;
                        case 'patient_created_date_':
                            if ($sVal && !preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}(\:[0-9]{2})?)?$/', $sVal)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                            }
                            $aLinePat['created_date'] = $sVal;
                            break;
                        case 'variant_edited_date_':
                            // FIXME; check for edited id?
                            if ($sVal && !preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}(\:[0-9]{2})?)?$/', $sVal)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                            }
                            $aLinePat2Var['edited_date'] = $sVal;
                            break;
                        case 'patient_edited_date_':
                            // FIXME; check for edited id?
                            if ($sVal && !preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}(\:[0-9]{2})?)?$/', $sVal)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                            }
                            $aLinePat['edited_date'] = $sVal;
                            break;
                        case 'ID_sort_':
                            // If empty, will be determined at the end of this line's run.
                            $aLineVar['sort'] = $sVal;
                            break;
                        case 'ID_allele_':
                            if ($sVal !== '') {
                                if (!array_key_exists($sVal, $_SETT['var_allele'])) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid allele ID.');
                                }
                            }
                            $aLinePat2Var['allele'] = $sVal;
                            break;
                        case 'ID_pathogenic_':
                            if ($sVal !== '') {
                                if ($sFormatVersion < '2000-040' && in_array($sVal, array('00', '01', '09', '10', '11', '19', '90', '91', '99'))) {
                                    // 2008-02-29; 2.0-04; Changed the whole pathogenicity ID code list.
                                    $sVal = $aTransformPathogenicity[$sVal{0}] . $aTransformPathogenicity[$sVal{1}];
                                }
                                // Empty value may not default to '00'!
                                $sVal = str_pad($sVal, 2, '0', STR_PAD_LEFT);
                                if (!array_key_exists($sVal, $_SETT['var_pathogenic_short'])) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid pathogenicity ID.');
                                }
                            }
                            $aLinePat2Var['pathogenic'] = $sVal;
                            break;
                        case 'ID_status_':
                            if ($sVal !== '') {
                                if (!array_key_exists($sVal, $_SETT['var_status'])) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid status ID.');
                                }
                            }
                            $aLinePat2Var['status'] = $sVal;
                            break;
                        case 'ID_submitterid_':
                            settype($sVal, 'int');
                            if (!in_array($sVal, $aIDsSubs)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid submitter ID.');
                            }
                            $aLinePat['submitterid'] = $sVal;
                            break;
                        case 'ID_variant_created_by_':
                            settype($sVal, 'int');
                            if (!in_array($sVal, $aIDsUsers)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid user ID.');
                            }
                            $aLinePat2Var['created_by'] = $sVal;
                            break;
                        case 'ID_patient_created_by_':
                            settype($sVal, 'int');
                            if (!in_array($sVal, $aIDsUsers)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid user ID.');
                            }
                            $aLinePat['created_by'] = $sVal;
                            break;
                        case 'ID_variant_edited_by_':
                            // FIXME; check for edited date?
                            settype($sVal, 'int');
                            if ($sVal && !in_array($sVal, $aIDsUsers)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid user ID.');
                            }
                            $aLinePat2Var['edited_by'] = $sVal;
                        case 'ID_patient_edited_by_':
                            // FIXME; check for edited date?
                            settype($sVal, 'int');
                            if ($sVal && !in_array($sVal, $aIDsUsers)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a valid user ID.');
                            }
                            $aLinePat['edited_by'] = $sVal;
                            break;
                        case 'ID_variantid_':
                            // 2008-12-23; 2.0-15; adapted by Gerard to allow for empty ID_variantid_ fields
                            if ($sVal && (!preg_match('/^[0-9]+$/', $sVal) || $sVal < 1)) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not an integer.');
                            }
                            settype($sVal, 'int');
                            $aLineVar['variantid'] = $sVal;
                            $aLinePat2Var['variantid'] = $sVal;
                            $nVariantID = $sVal;
                            break;
                        case 'ID_patientid_':
                            if (!preg_match('/^[0-9]+$/', $sVal) || $sVal < 1) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not an integer.');
                            }
                            settype($sVal, 'int');
                            $aLinePat['patientid'] = $sVal;
                            $aLinePat2Var['patientid'] = $sVal;
                            $nPatientID = $sVal;
                            break;
                        default:
                            // Variant or Patient column. Additional checking.
                            // Directly accessing $aColList, since the $_CURRDB methods are not going to help me here.

                            // Mandatory fields.
                            if ($_CURRDB->aColList[$sCol]['mandatory'] && !$sVal) {
                                // Mandatory col not filled in. Did we see this line before? If so, just ignore. Empty values are allowed for repeated data.
                                // 2008-11-05; 2.0-14; && $sCol != 'Variant/DBID' added by Gerard. That column is allowed to be empty and will be generated
                                // 2009-08-27; 2.0-21; Variant fields are mandatory only if published.
                                if (substr($sCol, 0, 8) == 'Variant/' && $aLine[array_search('ID_status_', $aColumns)] >= STATUS_MARKED && $sCol != 'Variant/DBID' && !array_key_exists((int) $aLine[array_search('ID_variantid_', $aColumns)], $aVariants)) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': missing value in mandatory "' . $sCol . '" column.');
                                }
                                // 2009-08-27; 2.0-21; Patient fields are always mandatory.
                                if (substr($sCol, 0, 8) == 'Patient/' && !array_key_exists((int) $aLine[array_search('ID_patientid_', $aColumns)], $aPatients)) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': missing value in mandatory "' . $sCol . '" column.');
                                }
                            }

                            // Field lengths.
                            $nMaxLength = $_CURRDB->getFieldLength($sCol);
                            $nLength = strlen($sVal);
                            if ($nMaxLength < $nLength) {
                                lovd_errorAdd('Error in line ' . $nLine . ': value of ' . $nLength . ' characters is too long for the "' . $sCol . '" column, which allows ' . $nMaxLength . ' characters.');
                            }

                            if ($sVal) {
                                // Field types.
                                $sType = $_CURRDB->getFieldType($sCol);
                                switch ($sType) {
                                    case 'INT':
                                        if (!preg_match('/^[0-9]+$/', $sVal)) {
                                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not an integer.');
                                        }
                                        break;
                                    case 'DEC':
                                        if (!is_numeric($sVal)) {
                                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not numeric.');
                                        }
                                        break;
                                    case 'DATETIME':
                                        if (!preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}\:[0-9]{2})?$/', $sVal)) {
                                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                                        }
                                        break;
                                    case 'DATE':
                                        if (!lovd_matchDate($sVal)) {
                                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column is not a date I understand.');
                                        }
                                        break;
                                }

                                // Regular expressions.
                                if ($_CURRDB->aColList[$sCol]['preg_pattern'] && !preg_match($_CURRDB->aColList[$sCol]['preg_pattern'], $sVal)) {
                                    lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column does not correspond to the required input pattern.');
                                }
                            }

                            // Store column value.
                            if (substr($sCol, 0, 7) == 'Variant') {
                                $aLineVar[$sCol] = $sVal;
                            } else {
                                $aLinePat[$sCol] = $sVal;
                            }
                    }

                    if (substr_count($_ERROR, "\n") >= $nMaxErrors) {
                        // Too many errors. Quit now.
                        lovd_errorAdd('Too many errors, stopping file processing.');
                        break 2;
                    }
                }

                // 2009-03-02; 2.0-16; ID_allele_ of course needs a default value.
                // ID_allele_ (pat2var).
                if (empty($aLinePat2Var['allele'])) {
                    $aLinePat2Var['allele'] = 0;
                }

                // 2009-02-09; 2.0-16; Move this up to prevent notices and failure to import variants.
                // 2009-01-27; 2.0-15; Generates a new variantid for each variant with an empty field.
                if (empty($aLineVar['variantid'])) {
                    // The ID_variantid_ field in the upload file is empty so we need to generate a variantid.
                    if (!$nMaxVariantID) {
                        list($nMaxVariantID) = mysql_fetch_row(mysql_query('SELECT MAX(variantid) FROM ' . TABLE_CURRDB_VARS));
                    }
                    $nVariantID = ++ $nMaxVariantID;
                    $aLineVar['variantid'] = $nVariantID;
                    $aLinePat2Var['variantid'] = $nVariantID;                    
                    $sPat2VarKey = $nVariantID . '|' . $nPatientID . '|' . $aLinePat2Var['allele'];
                }

                // Auto values!
                $sPat2VarKey = $nVariantID . '|' . $nPatientID . '|' . $aLinePat2Var['allele'];

                // 2008-02-12; 2.0-04
                // Now load the variant, if necessary. This will keep the $aVariants array small.
                if (array_key_exists($nVariantID, $aVariants) && $aVariants[$nVariantID]['indb'] && !isset($aVariants[$nVariantID]['sort'])) {
                    // I could have picked any other standard Variant column basically.
                    // Variant data is in the database, but not fetched yet. Do this now.
                    $z = mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_CURRDB_VARS . ' WHERE variantid = "' . $nVariantID . '"'));
                    $z = str_replace(array("\r\n", "\r", "\n"), array('\r\n', '\r', '\n'), $z);
                    $z['indb'] = true;
                    $aVariants[$nVariantID] = $z;
                }

                // 2007-09-21; 2.0-beta-09
                // Now load the patient, if necessary. This will keep the $aPatients array small.
                if (array_key_exists($nPatientID, $aPatients) && $aPatients[$nPatientID]['indb'] && !isset($aPatients[$nPatientID]['created_by'])) {
                    // I could have picked any other standard Patient column basically.
                    // Patient data is in the database, but not fetched yet. Do this now.
                    $z = mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_PATIENTS . ' WHERE patientid = "' . $nPatientID . '"'));
                    $z = str_replace(array("\r\n", "\r", "\n"), array('\r\n', '\r', '\n'), $z);
                    $z['indb'] = true;
                    $aPatients[$nPatientID] = $z;
                }

                // 2008-02-12; 2.0-04
                // Now load the patient2variant entry, if necessary. This will keep the $aPat2Var array small.
                if (array_key_exists($sPat2VarKey, $aPat2Var) && $aPat2Var[$sPat2VarKey]['indb'] && !isset($aPat2Var[$sPat2VarKey]['created_by'])) {
                    // I could have picked any other standard Pat2Var column basically.
                    // Pat2Var data is in the database, but not fetched yet. Do this now.
                    $z = mysql_fetch_assoc(mysql_query('SELECT * FROM ' . TABLE_PAT2VAR . ' WHERE symbol = "' . $_SESSION['currdb'] . '" AND variantid = "' . $nVariantID . '" AND patientid = "' . $nPatientID . '" AND allele = "' . $aLinePat2Var['allele'] . '"'));
                    $z = str_replace(array("\r\n", "\r", "\n"), array('\r\n', '\r', '\n'), $z);
                    $z['indb'] = true;
                    $aPat2Var[$sPat2VarKey] = $z;
                }

                // If this entry is already in the database, we don't have to do anything 'cause only the first instance
                // will be loaded into the database. The check for consistency (below) will only complain about an
                // unequal value if the second value is not empty.
                // Not in the database? Then auto-fill the value with a useful default!

                // ID_sort_ column (variant).
                if ($sMutationCol && !empty($aLineVar[$sMutationCol])) {
                    if (empty($aLineVar['sort'])) {
                        if (!array_key_exists($nVariantID, $aVariants)) {
                            // 2009-06-12; 2.0-19; Added exon column for better sort results.
                            // A bit crude; we're not checking if Variant/Exon exists, we just suppress a possible notice.
                            $aLineVar['sort'] = @lovd_sort($aLineVar[$sMutationCol], $aLineVar['Variant/Exon']);
                        }
                    } elseif ($sFormatVersion <= '2000-190' && substr($aLineVar['sort'], 4, 1) != '_') {
                        // 2009-08-28; 2.0-21; in older download files (LOVD version < 2.0-19), the exon number is not added to the sort code yet.
                        if (!empty($aLineVar['Variant/Exon'])) {
                            $aLineVar['sort'] = str_pad(substr(preg_replace('/^[^0-9]*([0-9]+).*$/', "$1", $aLineVar['Variant/Exon']), 0, 4), 4, '0', STR_PAD_LEFT) . '_' . $aLineVar['sort'];
                        } else {
                            $aLineVar['sort'] = '0000_' . $aLineVar['sort'];
                        }
                    }
                }

                // ID_pathogenic_ column (pat2var).
                if (!isset($aLinePat2Var['pathogenic']) || $aLinePat2Var['pathogenic'] === '') {
                    if (!array_key_exists($sPat2VarKey, $aPat2Var)) {
                        // 2008-05-28; 2.0-07; Changed default value from 99 to 55 (unknown).
                        $aLinePat2Var['pathogenic'] = '55';
                    }
                }

                // ID_status_ column (pat2var).
                if (!isset($aLinePat2Var['status']) || $aLinePat2Var['status'] === '') {
                    if (!array_key_exists($sPat2VarKey, $aPat2Var)) {
                        $aLinePat2Var['status'] = 1;
                    }
                }

                // variant_created_by_ (pat2var).
                if (empty($aLinePat2Var['created_by'])) {
                    if (!array_key_exists($sPat2VarKey, $aPat2Var)) {
                        $aLinePat2Var['created_by'] = (!empty($aLinePat['submitterid'])? 0 : $_AUTH['userid']);
                    }
                }

                // variant_created_date_ (pat2var).
                if (empty($aLinePat2Var['created_date'])) {
                    if (!array_key_exists($sPat2VarKey, $aPat2Var)) {
                        $aLinePat2Var['created_date'] = date('Y-m-d H:i:s');
                    }
                }

                // patient_created_by_ (patient).
                if (empty($aLinePat['created_by'])) {
                    if (!array_key_exists($nPatientID, $aPatients)) {
                        $aLinePat['created_by'] = (!empty($aLinePat['submitterid'])? 0 : $_AUTH['userid']);
                    }
                }

                // patient_created_date_ (patient).
                if (empty($aLinePat['created_date'])) {
                    if (!array_key_exists($nPatientID, $aPatients)) {
                        $aLinePat['created_date'] = date('Y-m-d H:i:s');
                    }
                }

                // 2008-11-13; 2.0-14 Added by Gerard. Generates a Variant/DBID.
                $sVariant = str_replace(array('(', ')', '?'), '', $aLineVar[$sMutationCol]);
                if (empty($aLineVar['Variant/DBID'])) {
                    // The Variant/DBID field in the upload file is empty so we need to generate it.
                    $sIDGenerated = lovd_fetchDBID($_SESSION['currdb'], $sVariant, $sMutationCol); // Finds an existing number or generates a new one BUT EACH TIME THE SAME

                    // If the variant is already in the database (ID_variantid_), then the other part of the code will check the DBID.
                    if (!array_key_exists($nVariantID, $aVariants)) {
                        // Not in the database yet.
                        if (!array_key_exists($sVariant, $aIDAssigned)) {
                            // This variant (DNA level) has not been found earlier in this imported file.
                            if (!in_array($sIDGenerated, $aIDAssigned)) {
                                // The generated DBID (IDgenerated) was not already used, so we can use it.
                                $aIDAssigned[$sVariant] = $sIDGenerated;
                            } else {
                                // The IDgenerated (Variant/DBID number) is already in use by a different variant, so we need a new IDGenerated
                                // This newly generated number is already in use, so add 1 to the highest value
                                $sIDGenerated = max($aIDAssigned);
                                $sIDGenerated ++;
                                $aIDAssigned[$sVariant] = $sIDGenerated;
                            }
                        } else {
                            // We've seen this variant (DNA level) before in the file. Se we know an ID already!
                            $sIDGenerated = $aIDAssigned[$sVariant];
                        }
                        $aLineVar['Variant/DBID'] = $sIDGenerated;
                    }

                } elseif (isset($aLineVar['Variant/DBID']) && preg_match('/^' . $_SESSION['currsymb'] . '_[0-9]{5}/', $aLineVar['Variant/DBID'])) {
                    // User has filled in an own DBID value that is correct. Let's use it.
                    $aIDAssigned[$sVariant] = substr($aLineVar['Variant/DBID'], 0, strlen($_SESSION['currsymb']) + 6);
                }

                // This information is not taken from the database.
                $aLineVar['indb'] = false;
                $aLinePat['indb'] = false;
                $aLinePat2Var['indb'] = false;
                $aLinePat2Var['symbol'] = $_SESSION['currdb'];

                // Save line number.
                $aLineVar['line'] = $nLine;
                $aLinePat['line'] = $nLine;
                $aLinePat2Var['line'] = $nLine;

                // Seen variant before? Check data.
                if (array_key_exists($nVariantID, $aVariants)) {
                    foreach ($aLineVar as $sCol => $sVal) {
                        // 2009-04-16; 2.0-18; Compare values trim()ed to make sure spaces are not a problem anymore.
                        if (!in_array($sCol, array('indb', 'line')) && $sVal && trim($sVal) != trim($aVariants[$nVariantID][$sCol])) {
                            // Col should not be indb or line; value should not be empty (otherwise, difference allowed); if value is different: throw error!
                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column does not match value "' . $aVariants[$nVariantID][$sCol] . '" from same variant entry ' . ($aVariants[$nVariantID]['indb']? 'in the database' : 'on line ' . $aVariants[$nVariantID]['line']) . '.');
                        }
                    }
                } else {
                    // Store information.
                    $aVariants[$nVariantID] = $aLineVar;
                    $nVariants ++;
                }

                // Seen patient before? Check data.
                if (array_key_exists($nPatientID, $aPatients)) {
                    foreach ($aLinePat as $sCol => $sVal) {
                        // 2009-04-16; 2.0-18; Compare values trim()ed to make sure spaces are not a problem anymore.
                        if (!in_array($sCol, array('indb', 'line')) && $sVal && trim($sVal) != trim($aPatients[$nPatientID][$sCol])) {
                            // Col should not be indb or line; value should not be empty (otherwise, difference allowed); if value is different: throw error!
                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column does not match value "' . $aPatients[$nPatientID][$sCol] . '" from same patient entry ' . ($aPatients[$nPatientID]['indb']? 'in the database' : 'on line ' . $aPatients[$nPatientID]['line']) . '.');
                        }
                    }
                } else {
                    // Store information.
                    $aPatients[$nPatientID] = $aLinePat;
                    $nPatients ++;
                }

                // Seen pat2var before? Check data.
                if (array_key_exists($sPat2VarKey, $aPat2Var)) {
                    foreach ($aLinePat2Var as $sCol => $sVal) {
                        // 2009-04-16; 2.0-18; Compare values trim()ed to make sure spaces are not a problem anymore.
                        if (!in_array($sCol, array('indb', 'line')) && $sVal && trim($sVal) != trim($aPat2Var[$sPat2VarKey][$sCol])) {
                            // Col should not be indb or line; value should not be empty (otherwise, difference allowed); if value is different: throw error!
                            lovd_errorAdd('Error in line ' . $nLine . ': value "' . htmlspecialchars($sVal) . '" in "' . $sCol . '" column does not match value "' . $aPat2Var[$sPat2VarKey][$sCol] . '" from same patients2variants entry ' . ($aPat2Var[$sPat2VarKey]['indb']? 'in the database' : 'on line ' . $aPat2Var[$sPat2VarKey]['line']) . '.');
                        }
                    }
                } else {
                    // Store information.
                    $aPat2Var[(int) $nVariantID . '|' . (int) $nPatientID . '|' . $aLinePat2Var['allele']] = $aLinePat2Var;
                    $nPat2Var ++;
                }
            }
            fclose($fInput);

            // 2008-02-12; 2.0-04
            // Save even more memory. Unset all the unnecessary stuff.
            reset($aVariants);
            reset($aPatients);
            reset($aPat2Var);
            while (list($sKey, $aVal) = each($aVariants)) {
                if (!isset($aVal['sort'])) {
                    unset($aVariants[$sKey]);
                }
            }
            while (list($sKey, $aVal) = each($aPatients)) {
                if (!isset($aVal['created_by'])) {
                    unset($aPatients[$sKey]);
                }
            }
            while (list($sKey, $aVal) = each($aPat2Var)) {
                if (!isset($aVal['created_by'])) {
                    unset($aPat2Var[$sKey]);
                }
            }



            if (!lovd_error()) {
                // Start importing from the memory!
                require ROOT_PATH . 'inc-top.php';
                lovd_printHeader('config', 'LOVD Configuration area');

                $nTotal = $nVariants + $nPatients + $nPat2Var;
                if (!$nTotal) {
                    print('      No entries found that need to be imported in the database. Either your uploaded file contains no variants, or all entries are already in the database.<BR>' . "\n\n");
                    require ROOT_PATH . 'inc-bot.php';
                    exit;
                }

                // Progress bar.
                print('      Input file verified. Importing entries...<BR><BR>' . "\n" .
                      '      <TABLE border="0" cellpadding="0" cellspacing="0" class="S11" width="250">' . "\n" .
                      '        <TR style="height : 15px;">' . "\n" .
                      '          <TD width="100%" style="border : 1px solid black;">' . "\n" .
                      '            <IMG src="' . ROOT_PATH . 'gfx/trans.png" alt="" title="" width="0%" height="15" id="lovd_progress_import" style="background : #AFC8FA;"></TD>' . "\n" .
                      '          <TD align="right" width="25"><INPUT type="text" id="lovd_progress_import_value" size="3" value="0%" style="border : 0px; margin : 0px; padding : 0px; text-align : right;"></TD></TR></TABLE><BR>' . "\n\n" .
                      '      <SCRIPT type="text/javascript">var progressImport = document.getElementById(\'lovd_progress_import\'); var progressImportValue = document.getElementById(\'lovd_progress_import_value\');</SCRIPT>' . "\n");

                // Import...
                $nProgressPrev = '0%';
                $n = 0;

                // If using transactional tables; begin transaction.
                if ($_INI['database']['engine'] == 'InnoDB') {
                    // FIXME; It's better to use 'START TRANSACTION', but that's only available from 4.0.11.
                    //   This works from the introduction of InnoDB in 3.23.
                    @mysql_query('BEGIN WORK');
                }



                // Variants.
                foreach ($aVariants as $nKey => $aVal) {
                    if ($aVal['indb']) {
                        continue;
                    }

                    $n++;
                    $sQ = 'INSERT INTO ' . TABLE_CURRDB_VARS . ' (';
                    $sCols = '';
                    $sVals = '';
                    foreach ($aVal as $sCol => $sVal) {
                        if (in_array($sCol, array('indb', 'line'))) {
                            continue;
                        }
                        $sCols .= ($sCols? ', ' : '') . '`' . $sCol . '`';
                        // 2009-06-11; 2.0-19; Added mysql_real_escape_string() to prevent SQL injection.
                        $sVals .= ($sVals? ', ' : '') . '"' . mysql_real_escape_string($sVal) . '"';
                    }
                    $sQ .= $sCols . ') VALUES (' . $sVals . ')';
                    $q = mysql_query($sQ);
                    if (!$q) {
                        // Save the mysql_error before it disappears.
                        $sError = mysql_error();
                        if ($_INI['database']['engine'] == 'InnoDB') {
                            @mysql_query('ROLLBACK');
                        }
                        lovd_dbFout('VariantImportA', $sQ, $sError);
                    }

                    $nProgress = round(($n*100)/$nTotal) . '%';
                    if ($nProgress != $nProgressPrev) {
                        print('      <SCRIPT type="text/javascript">progressImport.style.width = \'' . $nProgress . '\'; progressImportValue.value = \'' . $nProgress . '\';</SCRIPT>' . "\n");
                    }

                    flush();
                    usleep(1000);
                    $nProgressPrev = $nProgress;
                }



                // Patients.
                foreach ($aPatients as $nKey => $aVal) {
                    if ($aVal['indb']) {
                        continue;
                    }

                    $n++;
                    $sQ = 'INSERT INTO ' . TABLE_PATIENTS . ' (';
                    $sCols = '';
                    $sVals = '';
                    foreach ($aVal as $sCol => $sVal) {
                        if (in_array($sCol, array('indb', 'line'))) {
                            continue;
                        }
                        $sCols .= ($sCols? ', ' : '') . '`' . $sCol . '`';
                        // Quick fix; change edited* fields "" to NULL.
                        // 2009-06-11; 2.0-19; Added mysql_real_escape_string() to prevent SQL injection.
                        $sVals .= ($sVals? ', ' : '') . (substr($sCol, 0, 7) == 'edited_' && !$sVal? 'NULL' : '"' . mysql_real_escape_string($sVal) . '"');
                    }
                    $sQ .= $sCols . ') VALUES (' . $sVals . ')';
                    $q = mysql_query($sQ);
                    if (!$q) {
                        // Save the mysql_error before it disappears.
                        $sError = mysql_error();
                        if ($_INI['database']['engine'] == 'InnoDB') {
                            @mysql_query('ROLLBACK');
                        }
                        lovd_dbFout('VariantImportB', $sQ, $sError);
                    }

                    $nProgress = round(($n*100)/$nTotal) . '%';
                    if ($nProgress != $nProgressPrev) {
                        print('      <SCRIPT type="text/javascript">progressImport.style.width = \'' . $nProgress . '\'; progressImportValue.value = \'' . $nProgress . '\';</SCRIPT>' . "\n");
                    }

                    flush();
                    usleep(1000);
                    $nProgressPrev = $nProgress;
                }



                // Pat2Var data.
                foreach ($aPat2Var as $nKey => $aVal) {
                    if ($aVal['indb']) {
                        continue;
                    }

                    $n++;
                    $sQ = 'INSERT INTO ' . TABLE_PAT2VAR . ' (';
                    $sCols = '';
                    $sVals = '';
                    foreach ($aVal as $sCol => $sVal) {
                        if (in_array($sCol, array('indb', 'line'))) {
                            continue;
                        }
                        $sCols .= ($sCols? ', ' : '') . '`' . $sCol . '`';
                        // Quick fix; change edited* fields "" to NULL.
                        // 2009-06-11; 2.0-19; Added mysql_real_escape_string() to prevent SQL injection.
                        $sVals .= ($sVals? ', ' : '') . (substr($sCol, 0, 7) == 'edited_' && !$sVal? 'NULL' : '"' . mysql_real_escape_string($sVal) . '"');
                    }
                    $sQ .= $sCols . ') VALUES (' . $sVals . ')';
                    $q = mysql_query($sQ);
                    if (!$q) {
                        // Save the mysql_error before it disappears.
                        $sError = mysql_error();
                        if ($_INI['database']['engine'] == 'InnoDB') {
                            @mysql_query('ROLLBACK');
                        }
                        lovd_dbFout('VariantImportC', $sQ, $sError);
                    }

                    $nProgress = round(($n*100)/$nTotal) . '%';
                    if ($nProgress != $nProgressPrev) {
                        print('      <SCRIPT type="text/javascript">progressImport.style.width = \'' . $nProgress . '\'; progressImportValue.value = \'' . $nProgress . '\';</SCRIPT>' . "\n");
                    }

                    flush();
                    usleep(1000);
                    $nProgressPrev = $nProgress;
                }



                // If we don't do this, we haven't got anything in the DB... duh!
                if ($_INI['database']['engine'] == 'InnoDB') {
                    // Could this actually fail?!!??
                    @mysql_query('COMMIT');
                }

                // 2007-12-05; 2.0-02; Fixed bug #20 - Gene's "Last update" field not updated.
                lovd_setUpdatedDate($_SESSION['currdb']);

                // Done!!!
                // 2009-04-16; 2.0-18; Used $nPat2Var in stead of $nVariants to count *all* variants, not just the new ones.
                lovd_writeLog('MySQL:Event', 'VariantImport', $_AUTH['username'] . ' (' . mysql_real_escape_string($_AUTH['name']) . ') successfully imported ' . $nPat2Var . ' variant' . ($nVariants == 1? '' : 's') . ' and ' . $nPatients . ' patient' . ($nPatients == 1? '' : 's') . ' (' . $_SESSION['currdb'] . ')');

                print('      Done importing!<BR><BR>' . "\n\n");
                // 2009-03-18; 2.0-18; Tell the user which columns have been ignored.
                if ($aIgnoredColumns) {
                    // 2009-07-14; 2.0-20; Added htmlspecialchars() to prevent XSS attack through column names.
                    // 2009-08-27; 2.0-21; Funny, that just turned <BR> into &lt;BR&gt;. Fixed that.
                    print('      The following columns have been ignored, because they are not in the database:<BR>' . "\n" . nl2br(htmlspecialchars(implode($aIgnoredColumns, "\n"))) . "\n\n");
                }
                print('      <SCRIPT type="text/javascript">' . "\n" .
                      '        <!--' . "\n" .
                      '        setTimeout("window.location.href = \'' . PROTOCOL . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/config.php' . lovd_showSID() . '\'", ' . (3000 + (count($aIgnoredColumns) * 2000)) . ');' . "\n" .
                      '        // -->' . "\n" .
                      '      </SCRIPT>' . "\n\n");

                require ROOT_PATH . 'inc-bot.php';
                exit;
            }
        }
        @fclose($fInput);
    }
}



require ROOT_PATH . 'inc-top.php';
lovd_printHeader('config', 'LOVD Configuration area');

if (!isset($_GET['sent'])) {
    list($nMaxVar) = mysql_fetch_row(mysql_query('SELECT MAX(variantid) FROM ' . TABLE_CURRDB_VARS));
    list($nMaxPat) = mysql_fetch_row(mysql_query('SELECT MAX(patientid) FROM ' . TABLE_PATIENTS));
    if ($nMaxPat) {
        lovd_showInfoTable('If you\'re importing new variants or new patients, please note the available IDs:<BR>Variant:' . ($nMaxVar + 1) . ' and up<BR>Patient:' . ($nMaxPat + 1) . ' and up', 'information');
    }
}

print('      Please select the file containing the variant and patient data, that you wish to import into the ' . $_SESSION['currdb'] . ' gene.<BR>' . "\n" .
      '      <SPAN class="S11">(<A href="' . ROOT_PATH . 'docs/variant_and_patient_data/importing_text_files.php">Important information about the file format</A>)</SPAN><BR>' . "\n" .
      '      <BR>' . "\n\n");

lovd_errorPrint();

print('      <FORM action="' . $_SERVER['PHP_SELF'] . '?sent" method="post" enctype="multipart/form-data">' . "\n" .
      '        <INPUT type="hidden" name="MAX_FILE_SIZE" value="' . $nMaxSize . '">' . "\n" .
      '        <INPUT type="file" name="upload_file" size="30"><BR>' . "\n" .
      '        <INPUT type="submit" value="Import variants">' . "\n" .
      '      </FORM><BR>' . "\n\n");

lovd_showInfoTable('<SPAN class="S11">In some cases importing big files can lead to memory consumption problems. In case these errors are hidden, LOVD would return a blank screen. If this happens, split your import file into smaller chunks or ask your system administrator to allow PHP to use more memory.<BR><A href="http://www.lovd.nl/bugs/?do=details&id=17" target="_blank">More information</A>.</SPAN>', 'warning');

require ROOT_PATH . 'inc-bot.php';
?>
