<?php
/**
 * YANA library
 *
 * @package yana
 * @licence http://creativecommons.org/licenses/by-nc-sa/2.5/
 */

/**
 * StructureFile
 *
 * @access      public
 * @package     yana
 * @subpackage  io
 */
class StructureFile extends SmlFile
{
    /**
     * init
     *
     * @access public
     * @return boolean
     */
    function init()
    {
        if (parent::init() !== false)
        {
            foreach ($this->value['TABLES'] as $k => $v)
            {
                $this->value['TABLES'][$k]['PRIMARY_KEY'] = strtolower($v['PRIMARY_KEY']);
                if (isset($v['FOREIGN_KEYS'])) {
                    foreach ($v['FOREIGN_KEYS'] as $k1 => $v1)
                    {
                        $this->value['TABLES'][$k]['FOREIGN_KEYS'][$k1] = strtolower($v1);
                    }
                }
            }
            return $this->value;
        } else {
            return false;
        }
    }

    /**
     * getStructure
     *
     * @access public
     * @return mixed
     */
    function getStructure()
    {
        return $this->value;
    }

    /**
     * getSource
     *
     * @access public
     * @return mixed
     */
    function getSource()
    {
        return $this->source;
    }

    /**
     * isTable
     *
     * @access public
     * @param string $table
     * @return boolean
     */
    function isTable($table)
    {
        settype($table, 'string');
        $table = strtoupper($table);
        return isset($this->value['TABLES'][$table]);
    }

    /**
     * isColumn
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return boolean
     */
    function isColumn($table, $column)
    {
        settype($table, 'string');
        settype($column, 'string');
        $table = strtoupper($table);
        $column = strtoupper($column);
        return isset($this->value['TABLES'][$table]['CONTENT'][$column]);
    }

    /**
     * isForeignKey
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return boolean
     */
    function isForeignKey($table, $column)
    {
        settype($column, 'string');
        $column = strtoupper($column);
        $foreignKeys = $this->getForeignKeys($table);
        return isset($foreignKeys[$column]);
    }

    /**
     * isPrimaryKey
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return boolean
     */
    function isPrimaryKey($table, $column)
    {
        settype($column, 'string');
        return (strcasecmp($this->getPrimaryKey($table), $column) == 0);
    }

    /**
     * getForeignKeys
     *
     * @access public
     * @param string $table
     * @return array
     */
    function getForeignKeys($table)
    {
        settype($table, 'string');
        $table = strtoupper($table);
        if (isset($this->value['TABLES'][$table]['FOREIGN_KEYS'])) {
            return $this->value['TABLES'][$table]['FOREIGN_KEYS'];
        } else {
            return array();
        }
    }

    /**
     * getPrimaryKey
     *
     * @access public
     * @param string $table
     * @return string
     */
    function getPrimaryKey($table)
    {
        settype($table, 'string');
        $table = strtoupper($table);
        return strtolower($this->value['TABLES'][$table]['PRIMARY_KEY']);
    }

    /**
     * getLength
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return int
     */
    function getLength($table, $column)
    {
        settype($table, 'string');
        settype($column, 'string');
        $table = strtoupper($table);
        $column = strtoupper($column);
        if (isset($this->value['TABLES'][$table]['CONTENT'][$column]['LENGTH'])) {
            $length = $this->value['TABLES'][$table]['CONTENT'][$column]['LENGTH'];
            assert('is_numeric($length);');
            settype($length, 'integer');
        } else {
            $length = 0;
        }
        return $length;
    }

    /**
     * getType
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return string
     */
   function getType($table, $column)
    {
        settype($table, 'string');
        settype($column, 'string');
        $table = strtoupper($table);
        $column = strtoupper($column);
        return (string) @strtolower($this->value['TABLES'][$table]['CONTENT'][$column]['TYPE']);
    }

    /**
     * getColumns
     *
     * @access public
     * @param string $table
     * @return string
     */
   function getColumns($table)
    {
        settype($table, 'string');
        $table = strtoupper($table);
        return array_keys(array_change_key_case($this->value['TABLES'][$table]['CONTENT'],CASE_LOWER));
    }

    /**
     * getTableByForeignKey
     *
     * @access public
     * @param string $table
     * @param string $foreignKey
     * @return string
     */
    function getTableByForeignKey($table, $foreignKey)
    {
        settype($table, 'string');
        settype($foreignKey, 'string');
        $table = strtoupper($table);
        $foreignKey = strtoupper($foreignKey);
        return strtolower(@$this->value['TABLES'][$table]['FOREIGN_KEYS'][$foreignKey]);
    }

    /**
     * isStrict
     *
     * @access public
     * @return boolean
     */
    function isStrict()
    {
        return ($this->value['USE_STRICT'] === true || $this->value['USE_STRICT'] === 'true');
    }

    /**
     * getTables
     *
     * @access public
     * @return array
     */
    function getTables()
    {
        return array_keys(array_change_key_case($this->value['TABLES'],CASE_LOWER));
    }

    /**
     * isReadonly
     *
     * @access public
     * @param string $table
     * @param string $column
     * @return boolean
     */
    function isReadonly($table="",$column="")
    {
        settype($table, 'string');
        settype($column, 'string');
        $table = strtoupper($table);
        $column = strtoupper($column);
        if (!empty($this->value['READONLY'])) {
            return true;
        } else if (!empty($table) && !empty($column) && !empty($this->value['TABLES'][$table]['CONTENT'][$column]['READONLY'])) {
            return true;
        } else if (!empty($table) && !empty($this->value['TABLES'][$table]['READONLY'])) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * checkRow
     *
     * @access public
     * @param string $table
     * @param string &$row
     * @return boolean
     */
    function checkRow($table,&$row)
    {
        settype($table, 'string');
        $table = strtoupper($table);
        foreach ($this->value['TABLES'][$table]['CONTENT'] as $column => $b)
        {
            $column = strtolower($column);
            if (!empty($b['REQUIRED']) && !isset($row[$column])) {
                if (strcasecmp($b['REQUIRED'], 'auto') == 0) {
                    switch ($this->getType($table,$column)) {
                        case 'time':
                            $row[$column] = time();
                        break;
                        case 'ip':
                            $row[$column] = $_SERVER['REMOTE_ADDR'];
                        break;
                        default:
                            $row[$column] = false;
                        break;
                    }
                } else if (isset($b['DEFAULT'])) {
                    if (is_array($b['DEFAULT'])) {
                        $row[$column] = $this->untaintInput($table,$column, array_shift(array_keys($b['DEFAULT'])), YANA_ESCAPE_NONE);
                    } else if (is_scalar($b['DEFAULT'])) {
                        $row[$column] = $this->untaintInput($table,$column, $b['DEFAULT'], YANA_ESCAPE_NONE);
                    } else {
                        trigger_error("SYNTAX ERROR: Invalid default value for column '${column}'. The structure file is not valid.", E_USER_ERROR);
                    }
                    if ($b['TYPE'] == 'string') {
                        $row[$column] = Layout::replaceToken($row[$column]);
                    }
                } else {
                    trigger_error("SQL ERROR: A required attribute '${column}' has not been provided. Insert/Update operation aborted.", E_USER_WARNING);
                    return false;
                } /* end if */
            } /* end if */
        } /* end for */
        return true;
    }
    
    /**
     * constraint
     *
     * @access private
     * @param string $OPERATION
     * @param string $TABLE
     * @param string $FIELD (optional)
     * @param mixed $VALUE (optional)
     * @return boolean
     * @ignore
     */
    function constraint($OPERATION, $TABLE, $FIELD = NULL, $VALUE = NULL)
    {
        settype($OPERATION, "string");
        settype($TABLE,     "string");
        settype($FIELD,     "string");
        global $YANA;

        $OPERATION  = strtoupper($OPERATION);
        $TABLE      = strtoupper($TABLE);
        $FIELD      = strtoupper($FIELD);
        if (is_string($VALUE)) {
            $VALUE = stripslashes($VALUE);
        } else if (is_array($VALUE) && (is_null($FIELD) || empty($FIELD))) {
            $VALUE = changeCaseArray($VALUE, CASE_UPPER);
        }

        if (isset($YANA)) {
            $PERMISSION = $YANA->getVar("PERMISSION");
        } else {
            $PERMISSION = 0;      
        }
        $test = true;

        /* Database-wide constraints */
        if (isset($this->value['CONSTRAINT'][$OPERATION]) && is_array($this->value['CONSTRAINT'])) {
            $test = $test && $this->_eval($this->value['CONSTRAINT'][$OPERATION], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
        } else if (isset($this->value['CONSTRAINT']) && !is_array($this->value['CONSTRAINT'])) {
            $test = $test && $this->_eval($this->value['CONSTRAINT'], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
        } else {
            /* no database constraint present */
        }

        /* field specific constraints */
        if ($test != false && !empty($FIELD)) {
            if (isset($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT'][$OPERATION]) && is_array($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT'])) {
                $test = $test && $this->_eval($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT'][$OPERATION], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
            } else if (isset($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT']) && !is_array($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT'])) {
                $test = $test && $this->_eval($this->value['TABLES'][$TABLE]['CONTENT'][$FIELD]['CONSTRAINT'], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
            } else {
                /* no corresponding field specific constraint present */
            }
        }

        /* table-wide constraints */
        if ($test != false) {
            if (isset($VALUE)) {
                $global_value = $VALUE;
            }
            if (isset($this->value['TABLES'][$TABLE]['CONSTRAINT'][$OPERATION]) && is_array($this->value['TABLES'][$TABLE]['CONSTRAINT'])) {
                $test = $this->_eval($this->value['TABLES'][$TABLE]['CONSTRAINT'][$OPERATION], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
            } else if (isset($this->value['TABLES'][$TABLE]['CONSTRAINT']) && !is_array($this->value['TABLES'][$TABLE]['CONSTRAINT'])) {
                $test = $this->_eval($this->value['TABLES'][$TABLE]['CONSTRAINT'], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
            } else {
                /* no corresponding table specific constraint present */
            }
            /* when updating a row, check constraints of all fields involved */
            if (is_null($FIELD) || empty($FIELD)) {
                foreach ($this->value['TABLES'][$TABLE]['CONTENT'] as $FIELD => $option)
                {
                    if (isset($global_value[$FIELD])) {
                        $VALUE = $global_value[$FIELD];
                    } else {
                        $VALUE = NULL;
                    }
                    if (isset($option['CONSTRAINT'][$OPERATION])) {
                        switch ($OPERATION) {
                            case 'SELECT': case 'DELETE':
                                $test = $test && $this->_eval($option['CONSTRAINT'][$OPERATION], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
                            break;
                            case 'INSERT': case 'UPDATE':
                                if ($VALUE != NULL) { 
                                    $test = $test && $this->_eval($option['CONSTRAINT'][$OPERATION], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
                                }
                            break;
                            default:
                            break;
                        }
                    } else if (isset($option['CONSTRAINT']) && is_scalar($option['CONSTRAINT'])) {
                        $test = $test && $this->_eval($option['CONSTRAINT'], $OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE);
                    }
                }
            }
        }
        if ($test === false) {
            global $YANA;
            if (isset($YANA)) {
                $YANA->report( new log("DB WARNING: Constraint failed on table '${TABLE}' for operation '${OPERATION}'".(($FIELD!=NULL)?" on field '${FIELD}'":"").(($VALUE!=NULL)?" with value '${VALUE}'":"")."."));
            }
        }
        return $test;

    }

    /**
     * _eval
     *
     * @access private
     * @param string|bool $EVAL
     * @param string $OPERATION
     * @param int $PERMISSION
     * @param string $TABLE
     * @param string $FIELD (optional)
     * @param mixed $VALUE (optional)
     * @return boolean
     * @ignore
     */
    function _eval($EVAL, $OPERATION, $PERMISSION, $TABLE, $FIELD = NULL, $VALUE = NULL)
    {
        assert('(is_string($EVAL) && !empty($EVAL)) || is_bool($EVAL);');
        if (is_bool($EVAL)) {
            return $EVAL;
        } else if (empty($EVAL)) {
            return false;
        }
        $EVAL = " $EVAL ";
        $EVAL = preg_replace("/m(\/.*?\/\S*)/", "preg_match(\"\\1\", \$VALUE)", $EVAL);
        if (!preg_match("/^((-| |\!)?\\\$[\w\d_]+ | true | false | null | -?\d+ | | \&\&? | (preg_match|ereg|eregi)\(('[^']*'|\"[^\"]*\"), ?\\\$[\w\d_]+\) |[\&\|\!\~\-\*\/\%\+\<\>]| \"[^\"]*\" | \'[^\']*\' | \d+(\.\d*)? |(\=|\!|\<|\>)\=\=?| )*$/i", $EVAL)) {
            trigger_error("ERROR: in structure file ".$this->filename.", syntax error in constraint: ".trim($EVAL)."", E_USER_WARNING);
            return false;
        }
        
        $function_checkConstraint = create_function('$OPERATION,$PERMISSION,$TABLE,$FIELD,$VALUE', "if ($EVAL) return true; else return false;");

        if ($function_checkConstraint($OPERATION, $PERMISSION, $TABLE, $FIELD, $VALUE) === true ) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * untaintInput
     *
     * @access public
     * @param string $table
     * @param string $column
     * @param mixed $value
     * @param int $escape
     * @return mixed
     */
    function untaintInput($table, $column, $value, $escape = YANA_ESCAPE_NONE)
    {
        return untaintInput($value, (string) $this->getType($table,$column), (int) $this->getLength($table,$column), $escape);
    }
    
    /**
     * addStructure
     *
     * Try to extract some information on the structure of
     * a database from the information provided by PEAR-DB's
     * tableInfo() function.
     *
     * see bufferedDbStream::buildStructure() for additional
     * information on the input
     *
     * @uses void buildStructure()
     *
     * @access public
     * @param string $table current table name
     * @param array $array value returned by getTableInfo() -function
     * @ignore
     */
    function addStructure($table, $array)
    {
        assert('is_array($array);');
        if (!is_array($array)) {
            return false;
        } else {
            $array = changeCaseArray($array,CASE_UPPER);
            $config   = array("USE_STRICT" => "true", "READONLY" => "false", "TABLES" => array());
            for ($i = 0; $i < $array['NUM_FIELDS']; $i++)
            {
               $name  = $array[$i]['NAME'];
               $flags = explode(" ", $array[$i]['FLAGS']);
               $config['TABLES'][$table]['CONTENT'][$name] = array();
               switch ($array[$i]['TYPE']) {
                   case 'blob':
                       $array[$i]['TYPE']                          = "text";
                   break;
                   case 'tinyint': case 'bigint': case 'int':
                       $array[$i]['TYPE']                          = "integer";
                   break;
                   case 'bool':
                       $array[$i]['TYPE']                          = "boolean";
                   break;
                   case 'double': case 'numeric':
                       $array[$i]['TYPE']                          = "float";
                   break;
                   case 'timestamp':
                       $array[$i]['TYPE']                          = "time";
                       $array[$i]['REQUIRED']                      = "AUTO";
                   break;
                   case 'datetime': case 'string': case 'varchar':
                       $array[$i]['TYPE']                          = "string";
                   break;
                   /* more ? */
                   default:
                       /* convert unrecognized type to string */
                       $array[$i]['TYPE']                          = "string";
                   break;

               }
               if (!empty($array[$i]['TYPE'])) {
                   $config['TABLES'][$table]['CONTENT'][$name]['TYPE'] = $array[$i]['TYPE'];
               }
               if (!empty($array[$i]['LEN'])) {
                   $config['TABLES'][$table]['CONTENT'][$name]['LENGTH']= $array[$i]['LEN'];
               }
               $config['TABLES'][$table]['CONTENT'][$name]['REQUIRED'] = in_array('not_null', $flags);
               if (in_array('primary_key', $flags)) {
                    $config['TABLES'][$table]['PRIMARY_KEY']             = $name;
                    $config['TABLES'][$table]['CONTENT'][$name]['HIDDEN']= "true";
               }
            }
            if (isset($this->value)) {
                $this->value = mergeArrays($this->value, $config);
            } else {
                $this->value = $config;
            }
        }
    }
}

?>
