<?php
/*******************************************************************************
 *
 * LEIDEN OPEN VARIATION DATABASE (LOVD)
 *
 * Created     : 2006-08-09
 * Modified    : 2009-06-19
 * For LOVD    : 2.0-19
 *
 * Access      : Public
 * Purpose     : Handle information for the current gene's variant table.
 *
 * 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;
}



class CurrDB {
    // Some member variables.
    // FIXME; for PHP5 we would use a private member variable. Backwards compatible with PHP4.X.
    var $aColList = array(); // List of columns. BEWARE that config_import.php directly accesses this variable.
    var $bCurrDB = true;     // Need to have a gene selected?
    var $sMutationCol;       // Name of the column probably used for mutation description.
    var $sSymbol = '';       // Symbol of the gene we are working on.
    var $sSymbolShort = '';  // Symbol (short version) of the gene we are working on.





    // Methods.
    function CurrDB ($bCurrDB = true, $sSymbol = '')
    {
        // PHP 4 constructor.
        global $_SETT;

        // Gather standard info necessary for the other methods.
        if (!$bCurrDB) {
            $this->bCurrDB = false;
        }

        // When not using the $_SESSION['currdb'] gene...
        if ($sSymbol !== '' && $sSymbol) {
            // We'll skip the checking if the gene exists for now... __construct() will complain.
            $this->sSymbol = $sSymbol;
        } elseif (isset($_SESSION['currdb'])) {
            $this->sSymbol = $_SESSION['currdb'];
        }

        // Create symbol from gene db name...
        if ($this->sSymbol) {
            $this->sSymbolShort = substr($this->sSymbol, 0, strpos($this->sSymbol . '_', '_'));
        }



        if ($this->bCurrDB && !$this->sSymbol) {
            // Called this class without actually having selected a gene...
            lovd_displayError('NoCurrDB', 'Currently, no gene was selected or sent, but the currdb class has been initiated. This is a bug in LOVD or in one of it\'s modules. Please <A href="' . $_SETT['upstream_URL'] . 'bugs/" target="_blank">file a bug</A> and include the below messages to help us solve the problem.' . "\n" .
                                                      'Debug: ' . $_SERVER['PHP_SELF'] . ($_SERVER['QUERY_STRING']? '?' . $_SERVER['QUERY_STRING'] : ''));
        }

        // If creating a gene, LOVD wants to know info about the columns in the system.
        if ($this->bCurrDB) {
            // The last part (WHERE ...) is needed to make sure we don't get any info about columns which are not in use.
            $sQ = 'SELECT t1.*, t2.* FROM ' . TABLE_COLS . ' AS t1 LEFT JOIN ' . TABLEPREFIX . '_' . $this->sSymbol . '_columns AS t2 USING (colid) WHERE t1.colid = t2.colid ORDER BY t2.col_order';
        } else {
            // The last part (WHERE ...) is needed to make sure we don't get any info about columns which are not in use.
            $sQ = 'SELECT t1.* FROM ' . TABLE_COLS . ' AS t1 WHERE t1.standard = 1';
        }

        $q = mysql_query($sQ);
        if (!$q) {
            lovd_dbFout('CURRDB::__construct()', $sQ, mysql_error());
        }
        while ($z = mysql_fetch_assoc($q)) {
            $this->aColList[$z['colid']] = $z;
            $this->aColList[$z['colid']]['form_details'] = explode('|', $z['form_type']);
        }

        // Merge this with the patient columns, instead of a separate class for those.
        // The last part (WHERE ...) is needed to make sure we don't get any info about columns which are not in use.
        $sQ = 'SELECT t1.*, t2.* FROM ' . TABLE_COLS . ' AS t1 LEFT JOIN ' . TABLE_PATIENTS_COLS . ' AS t2 USING (colid) WHERE t1.colid = t2.colid ORDER BY t2.col_order';

        $q = mysql_query($sQ);
        if (!$q) {
            lovd_dbFout('CURRDB::__construct()', $sQ, mysql_error());
        }
        while ($z = mysql_fetch_assoc($q)) {
            $this->aColList[$z['colid']] = $z;
            $this->aColList[$z['colid']]['form_details'] = explode('|', $z['form_type']);
        }



        // Find column where the mutation is described: DNA, RNA or Protein?
        if ($this->colExists('Variant/DNA')) {
            $this->sMutationCol = 'Variant/DNA';
        } elseif ($this->colExists('Variant/RNA')) {
            $this->sMutationCol = 'Variant/RNA';
        } elseif ($this->colExists('Variant/Protein')) {
            $this->sMutationCol = 'Variant/Protein';
        } else {
            $this->sMutationCol = false;
        }
    }





    function buildFormTable ($sTable = '', $sColRequested = '')
    {
        // Builds the array needed to display the form.
        $aTable = array();

        if (!in_array(strtolower($sTable), array('variant', 'patient'))) {
            $sTable = '';
        } else {
            $sTable = ucfirst($sTable);
        }

        // Gather the custom link information.
        // Backwards compatible; using GROUP_CONCAT whould have helped now... Alas, only for MySQL 4.1 and up.
        $qLinks = mysql_query('SELECT c2l.colid, c2l.linkid, l.linkname FROM ' . TABLE_COLS2LINKS . ' AS c2l LEFT JOIN ' . TABLE_LINKS . ' AS l USING (linkid) WHERE c2l.colid LIKE "' . ucfirst($sTable) . '/%"');
        while ($z = mysql_fetch_assoc($qLinks)) {
            // 2008-02-29; 2.0-04; Only if column is active, duh!
            if (isset($this->aColList[$z['colid']])) {
                // No custom link registered for this column, yet.
                if (empty($this->aColList[$z['colid']]['custom_links'])) {
                    $this->aColList[$z['colid']]['custom_links'] = array();
                }

                $this->aColList[$z['colid']]['custom_links'][$z['linkid']] = $z['linkname'];
            }
        }

        foreach ($this->aColList as $sCol => $aCol) {
            if ($sTable && substr($sCol, 0, strlen($sTable)) != $sTable) {
                // Column is not in the requested table.
                continue;

            } elseif (!empty($sColRequested) && $sCol != $sColRequested) {
                continue;

            } else {
                // Feature; if field is a selection list, and no select_values are available, it will be a text-field.
                if ($aCol['form_details'][1] == 'select' && !trim($aCol['select_options'])) {
                    $aCol['form_details'] = array($aCol['form_details'][0], 'text', '30');

                    // 2007-06-22; 2.0-beta-05; That's nice, but the $_POST data is already an array when editing!!!
                    if (!empty($_POST[$sCol]) && is_array($_POST[$sCol])) {
                        $_POST[$sCol] = implode(';', $_POST[$sCol]);
                    }

                    // 2007-07-10; 2.0-beta-06; Great... now add a warning to make sure you separate values by ';'.
                    $aCol['description_form'] = 'Please separate values by a semi-colon (;).' . ($aCol['description_form']? '<BR>' . $aCol['description_form'] : '');
                }

                // Build what type of form entry?
                if ($aCol['form_details'][1] != 'select') {
                    // No select entry; add entry name.
                    $aEntry = array();
                    foreach ($aCol['form_details'] as $key => $val) {
                        // 2008-09-22; 2.0-12; Hide (Optional) tags on dynamic variant search form.
                        if (!$key && !$aCol['mandatory'] && lovd_getProjectFile() != '/variants_search.php') {
                            // Add '(Optional)'.
                            $val .= ' (Optional)';
                        } elseif ($key == 2) {
                            // Add the form entry name.
                            $aEntry[] = $sCol;
                        }
                        $aEntry[] = $val;
                    }
                    
                    $aTable[] = $aEntry;

                } else {
                    // Select entries are modified a little more - need source data.
                    $aEntry = array();
                    foreach ($aCol['form_details'] as $key => $val) {
                        // 2008-09-22; 2.0-12; Hide (Optional) tags on dynamic variant search form.
                        if (!$key && !$aCol['mandatory'] && lovd_getProjectFile() != '/variants_search.php') {
                            // Add '(Optional)'.
                            $val .= ' (Optional)';
                        } elseif ($key == 2) {
                            // Add the form entry name.
                            $aEntry[] = $sCol;
                        } elseif ($key == 3) {
                            // Add the form entry data.
                            $a = explode("\r\n", $aCol['select_options']);
                            $aData = array();
                            foreach ($a as $sLine) {
                                if (substr_count($sLine, '=')) {
                                    list($sKey, $sVal) = explode('=', $sLine, 2);
                                    $sVal = lovd_shortenString(trim($sVal), 75); // 2007-08-20; 2.0-beta-08 && 2007-12-21; 2.0-02
                                    $aData[trim($sKey)] = $sVal;
                                } else {
                                    $sVal = trim($sLine);
                                    $sVal = lovd_shortenString($sVal, 75); // 2007-08-20; 2.0-beta-08 && 2007-12-21; 2.0-02
                                    $aData[$sVal] = $sVal;
                                }
                            }

                            // 2007-07-10; 2.0-beta-06; Add currently filled in data if
                            // it's not in the selection_values, or else we'll lose it!
                            // 2007-08-08; 2.0-beta-07; just great... add the check to
                            // prevent Notices + warnings about non-existent indexes.
                            // 2007-09-28; 2.0-beta-09; Separated the is_array() check
                            // inside the if () statement to include this feature for
                            // drop down lists where only one option can be selected.
                            if (!empty($_POST[$sCol])) {
                                if (is_array($_POST[$sCol])) {
                                    $aPOST = $_POST[$sCol]; // Multiple selection list.
                                } else {
                                    $aPOST = array($_POST[$sCol]); // Drop down list.
                                }
                                foreach ($aPOST as $sOption) {
                                    if ($sOption && !array_key_exists($sOption, $aData)) {
                                        // Add entry!
                                        $aData[$sOption] = $sOption;
                                    }
                                }
                            }

                            $aEntry[] = $aData;
                        }

                        if ($val == 'false') {
                            $val = false;
                        } elseif ($val == 'true') {
                            $val = true;
                        }
                        $aEntry[] = $val;
                    }

                    // Shorten selection list if source data is shorter.
                    if ($aEntry[3] > 1) {
                        // Size > 1.
                        $nItems = count($aEntry[4]);
                        if ($nItems < $aEntry[3]) {
                            // Set size = number of options.
                            $aEntry[3] = $nItems;
                        }
                    }

                    $aTable[] = $aEntry;
                }

                // Any custom links we want to mention?
                if (isset($aCol['custom_links'])) {
                    $sLinks = '';
                    foreach ($aCol['custom_links'] as $nLink => $sLink) {
                        $sLinks .= ($sLinks? ', ' : '') . '<A href="#" onclick="lovd_openWindow(\'' . ROOT_PATH . 'links.php?view=' . $nLink . '&amp;col=' . rawurlencode($sCol)  . lovd_showSID(true, true) . '\', \'LinkView\', \'800\', \'200\'); return false;">' . $sLink . '</A>';
                    }
                    $aTable[] = array('', 'print', '<SPAN class="S11">(Active custom link' . (count($aCol['custom_links']) == 1? '' : 's') . ' : ' . $sLinks . ')</SPAN>');
                }

                // Need to add description?
                if ($aCol['description_form']) {
                    $aTable[] = array('', 'print', '<SPAN class="form_note">' . $aCol['description_form'] . '</SPAN>');
                }
            }
        }

        return $aTable;
    }





    function buildLegend ($sType)
    {
        // Builds legend (short and full) and returns the string.
        $sLegend = '';

        if (!in_array($sType, array('short', 'full'))) {
            lovd_writeLog('MySQL:Error', 'CURRDB::Lib', $_SERVER['PHP_SELF'] . ' returned CURRDB::buildLegend sType error (' . $sType . ')');
            $sType = 'short';
        }

        foreach ($this->aColList as $sCol => $aCol) {
            // Return something only if there is a description in the database.
            if ($aCol['description_legend_' . $sType]) {
                // FIXME; there is something hardcoded in here that adds the symbol to the legend. But what about the column head?
                //    Maybe we should have this column updated before we insert it for the gene. Then only config_genes needs to be updated.
                //    This codes keeps $this->sSymbolShort installed.
                if ($sType == 'short') {
                    $sLegend .= ($sLegend? ' ' : '') . '<B>' . ($sCol == 'Variant/DBID'? $this->sSymbolShort . ' ' : '') . $aCol['head_column'] . ':</B> ' . $aCol['description_legend_' . $sType];
                } else {
                    $sLegend .= '      <B>' . ($sCol == 'Variant/DBID'? $this->sSymbolShort . ' ' : '') . $aCol['head_column'] . ':</B> ' . $aCol['description_legend_' . $sType] . '<BR>';
                    if (!empty($aCol['select_options'])) {
                        $sLegend .= "\n" .
                                    '      <UL style="margin-top : 0px;">' . "\n";
                        $aOptions = preg_split('/[\r\n]+/', trim($aCol['select_options']));
                        foreach ($aOptions as $sOption) {
                            $sLegend .= '        <LI>' . $sOption . '</LI>' . "\n";
                        }
                        $sLegend .= '      </UL>';
                    }
                    $sLegend .= '<BR>' . "\n\n";
                }
            }
        }
        return $sLegend;
    }





    function buildOrderList ()
    {
        // Build the array needed to provide the sorting functionality.
        $aOrderList = array();

        $aCols = array_keys($this->aColList);
        foreach ($aCols as $sCol) {
            $aOrderList[$sCol] = array('`' . $sCol . '`', 'ASC');
        }

        return $aOrderList;
    }





    function buildSearchList ($sType = '')
    {
        // Build the array needed to provide the search functionality.
        $aSearchList = array();

        $aCols = array_keys($this->aColList);
        foreach ($aCols as $sCol) {
            if (!$sType || $sType == $this->aColList[$sCol]['form_details'][1]) {
                $aSearchList['search_' . $sCol] = '`' . $sCol . '`';
            }
        }

        return $aSearchList;
    }





    function buildTable ($sType = 'list')
    {
        // Build the array needed to build the variant table.
        $aTable = array();

        if (!in_array($sType, array('list', 'detail'))) {
            $sType = 'list';
        }

        foreach ($this->aColList as $sCol => $aCol) {
            if ($sType == 'list') {
                $aTable[$sCol] = array($aCol['head_column'], $aCol['width']);
            } else {
                $aTable[$sCol] = $aCol['head_column'];
            }
        }

        return $aTable;
    }





    function checkInputLength ()
    {
        // Checks if field input is not too long for the field.

        foreach ($_POST as $sCol => $val) {
            if ($this->colExists($sCol)) {
                $nMaxLength = $this->getFieldLength($sCol);
                if (is_array($val)) {
                    $val = implode(';', $val);
                }
                $nLength = strlen($val);
                if ($nMaxLength < $nLength) {
                    lovd_errorAdd('The \'' . $this->aColList[$sCol]['form_details'][0] . '\' field is limited to ' . $nMaxLength . ' characters, you entered ' . $nLength . '.');
                }
            }
        }
    }





    function checkInputRegExp ()
    {
        // Checks if field input corresponds to the given regexp pattern.

        foreach ($_POST as $sCol => $val) {
            if ($this->colExists($sCol) && $this->aColList[$sCol]['preg_pattern'] && !empty($_POST[$sCol])) {
                if (!preg_match($this->aColList[$sCol]['preg_pattern'], $val)) {
                    lovd_errorAdd('The input in the \'' . $this->aColList[$sCol]['form_details'][0] . '\' field does not correspond to the required input pattern.');
                }
            }
        }
    }





    function checkInputType ()
    {
        // Checks if field input is of the correct type.

        foreach ($_POST as $sCol => $val) {
            if ($this->colExists($sCol)) {
                $sType = $this->getFieldType($sCol);
                switch ($sType) {
                    case 'INT':
                        if (!preg_match('/^[0-9]*$/', $val)) {
                            lovd_errorAdd('The field \'' . $this->aColList[$sCol]['form_details'][0] . '\' must contain an integer.');
                        }
                        break;
                    case 'DEC':
                        if (!is_numeric($val)) {
                            lovd_errorAdd('The field \'' . $this->aColList[$sCol]['form_details'][0] . '\' must contain a number.');
                        }
                        break;
                    case 'DATETIME':
                        if (!preg_match('/^[0-9]{4}[.\/-][0-9]{2}[.\/-][0-9]{2}( [0-9]{2}\:[0-9]{2}\:[0-9]{2})?$/', $val)) {
                            lovd_errorAdd('The field \'' . $this->aColList[$sCol]['form_details'][0] . '\' must contain a date, possibly including a time.');
                        }
                        break;
                    case 'DATE':
                        if (!lovd_matchDate($val)) {
                            lovd_errorAdd('The field \'' . $this->aColList[$sCol]['form_details'][0] . '\' must contain a date.');
                        }
                        break;
                }
            }
        }
    }





    function checkMandatory ($sTable = '')
    {
        // Checks if mandatory fields are filled in ($_POST data).
        if (!in_array(strtolower($sTable), array('variant', 'patient'))) {
            $sTable = '';
        } else {
            $sTable = ucfirst($sTable);
        }

        foreach ($this->aColList as $sCol => $aCol) {
            if ($sTable && substr($sCol, 0, strlen($sTable)) != $sTable) {
                // Column is not in the requested table.
                continue;
            } elseif ($aCol['mandatory'] && empty($_POST[$sCol])) {
                lovd_errorAdd('Please fill in the \'' . $aCol['form_details'][0] . '\' field.');
            }
        }
    }





    function colExists ($sCol)
    {
        // Returns true if col exists.
        return (isset($this->aColList[$sCol]));
    }





    function getColList ($sTable = '')
    {
        // Returns list of custom column names installed for this table.
        if (!in_array(strtolower($sTable), array('variant', 'patient'))) {
            $sTable = '';
        } else {
            $sTable = ucfirst($sTable);
        }

        $aCols = array_keys($this->aColList);
        foreach ($aCols as $key => $sCol) {
            if ($sTable && substr($sCol, 0, strlen($sTable)) != $sTable) {
                // Column is not in the requested table.
                unset($aCols[$key]);
            }
        }

        // Fix indexes not starting at 0, breaking automated queries.
        return array_values($aCols);
    }





    function getDefaultValue ($sCol)
    {
        // Returns the column type, so the input can be checked.
        // 2009-02-16; 2.0-16; Introducing default values.
        if (!$this->colExists($sCol)) {
            return '';
        }

        // 2009-02-16; 2.0-16; Introducing default values.
        if (preg_match('/ DEFAULT ([0-9]+|"[^"]+")/', $this->aColList[$sCol]['mysql_type'], $aRegs)) {
            // Process default values.
            return trim($aRegs[1], '"');
        } else {
            return '';
        }
    }





    function getFieldLength ($sCol)
    {
        // Returns the maximum number of characters a column can hold, so the input can be checked.
        if (!$this->colExists($sCol)) {
            return 0;
        }

        $sColType = $this->aColList[$sCol]['mysql_type'];
        if (preg_match('/^((TINY|SMALL|MEDIUM|BIG)?INT|(VAR)?CHAR)\(([0-9]+)\)/', $sColType, $aRegs) && is_numeric($aRegs[4])) {
            return $aRegs[4];

        } elseif (preg_match('/^DEC\(([0-9]+),([0-9]+)\)/', $sColType, $aRegs) && is_numeric($aRegs[1]) && is_numeric($aRegs[2])) {
            return ($aRegs[1] + 1);

        } elseif (preg_match('/^(TINY|MEDIUM|LONG)?(TEXT|BLOB)/', $sColType, $aRegs)) {
            switch ($aRegs[1]) {
                case 'TINY':
                    return 256; // 2^8; 1 byte
                case 'MEDIUM':
                    return 16777216; // 2^24; 3 bytes
                case 'LONG':
                    return 4294967296; // 2^32; 4 bytes
                default:
                    return 65536; // 2^16; 2 bytes
            }
        } elseif (preg_match('/^DATE(TIME)?/', $sColType, $aRegs)) {
            // 2009-06-19; 2.0-19; added DATE and DATETIME datatypes.
            return (10 + (!empty($aRegs[1])? 9 : 0));
        }

        return 0;
    }





    function getFieldType ($sCol)
    {
        // Returns the column type, so the input can be checked.
        if (!$this->colExists($sCol)) {
            return false;
        }

        $sColType = $this->aColList[$sCol]['mysql_type'];
        if (preg_match('/^((VAR)?CHAR\([0-9]+\)|(TINY|MEDIUM|LONG)?(TEXT|BLOB))/', $sColType)) {
            return 'CHAR';
        } elseif (preg_match('/^(TINY|SMALL|MEDIUM|BIG)?INT\([0-9]+\)/', $sColType)) {
            return 'INT';
        } elseif (preg_match('/^DEC\([0-9]+,[0-9]+\)/', $sColType)) {
            return 'DEC';
        } elseif (preg_match('/^DATETIME/', $sColType)) {
            return 'DATETIME';
        } elseif (preg_match('/^DATE/', $sColType)) {
            return 'DATE';
        }
        return false;
    }





    function getMutationCol ()
    {
        // Returns the mutation description column: DNA, RNA or Protein?
        return $this->sMutationCol;
    }





    function hideCols ($sType = 'public', $bCurator = false)
    {
        // Remove columns from the column array based on the 'public' or
        // 'public_form' setting and the rights of the user.
        if (!in_array($sType, array('public', 'public_form'))) {
            // Hide columns based on...?
            $sType = 'public';
        }

        foreach ($this->aColList as $sCol => $aCol) {
            // Only if one is allowed to see this column; remove it otherwise.
            if (!$aCol[$sType] && !$bCurator) {
                unset($this->aColList[$sCol]);
            }
        }
    }





    function hideColsByTable ($sTable = '')
    {
        // Remove columns from the column array based on the table the column is
        // from.
        if (!in_array(strtolower($sTable), array('variant', 'patient'))) {
            $sTable = '';
        } else {
            $sTable = ucfirst($sTable);
        }

        $aCols = array_keys($this->aColList);
        foreach ($aCols as $sCol) {
            if ($sTable && substr($sCol, 0, strlen($sTable)) != $sTable) {
                // Column is not in the requested table.
                unset($this->aColList[$sCol]);
            }
        }
    }





    function initDefaultValues ($sTable = '')
    {
        // Initiate default values of fields in $_POST.
        // 2009-02-16; 2.0-16; Introducing default values.
        if (!in_array(strtolower($sTable), array('variant', 'patient'))) {
            $sTable = '';
        } else {
            $sTable = ucfirst($sTable);
        }

        $aCols = array_keys($this->aColList);
        foreach ($aCols as $sCol) {
            if ($sTable && substr($sCol, 0, strlen($sTable)) != $sTable) {
                // Column is not in the requested table.
                continue;
            }

            // Otherwise, fill $_POST with the column's default value.
            $_POST[$sCol] = $this->getDefaultValue($sCol);
        }
    }





    function transformSelectValues (& $zData, $bExplode = false)
    {
        // Transforms the select lists' values.
        foreach ($zData as $sCol => $val) {
            if ($this->colExists($sCol) && $this->aColList[$sCol]['form_details'][1] == 'select') {
                // 2007-01-31; 2.0-alpha-06; Fixes #12.
                // Explode only on multiple select columns.
                if ($bExplode && $this->aColList[$sCol]['form_details'][4] == 'true') {
                    // Feeding the form, returning array.
                    $zData[$sCol] = explode(';', $val);
                } else {
                    // Showing values in table.
                    $zData[$sCol] = str_replace(';', ', ', $val);
                }
            }
        }
    }
}
?>
