<?php
/**
 * @copyright (c) Kufer Software GmbH, Loisachstrasse 4, D-84453 Mühldorf
 */


abstract class DBOperation
{
  
  public $resultset;

  
  public $actualrow;

  
  public $currentRow;

  
  public $connection = false;

  
  public $connectionInfo;

  
  public $dbparser;

  
  public $dbnr;

  
  public static $dbary = array(1 => null, 2 => null);

  
  public function __construct($connectionInfo, $pDBnr)
  {
    $this->clear();

    
    

    $this->connectionInfo = $connectionInfo;
    $this->dbnr = $pDBnr;
  }

  public function __destruct()
  {
    
     
     
  }

  
  public static function getInstance($pDBnr, $pNewInstance = false, $pReConnect = false)
  {
    $connectionInfo = DBConnectionInfo::getInstance();

    
    if ($pReConnect) {
      if (self::$dbary[$pDBnr] === null) {
        self::$dbary[$pDBnr] = DBOperation::getNewDBOperation($connectionInfo, $pDBnr);
        if (LOG_DB) {
          Logger::logg('Neues DBObjekt wird benutzt (id = ' . (self::$dbary[$pDBnr]->GetID()) . ')', 9);
        }
      } else {
        if (self::$dbary[$pDBnr] instanceof DBOperationMySQLi) {
          if (LOG_DB) {
            Logger::logg('DBObjekt wird per getInstance mit $pReConnect neu verbunden (id = ' . (self::$dbary[$pDBnr]->GetID()) . ')', 9);
          }
        }
      }
      
      self::$dbary[$pDBnr]->closeConnection();
    }

    if ($pNewInstance) {
      $obj = DBOperation::getNewDBOperation($connectionInfo, $pDBnr);
      if ($obj instanceof DBOperationMySQLi) {
        if (LOG_DB) {
          Logger::logg('DBObjekt wird per getInstance mit $pNewInstance erzeugt (id = ' . $obj->GetID() . ')', 9);
        }
      }

      return $obj;
    }

    if (! (is_numeric($pDBnr) && in_array($pDBnr, array(1, 2)))) {
      return null;
    }

    if (self::$dbary[$pDBnr] === null) {
      self::$dbary[$pDBnr] = DBOperation::getNewDBOperation($connectionInfo, $pDBnr);
      if (self::$dbary[$pDBnr] instanceof DBOperationMySQLi) {
        if (LOG_DB) {
          Logger::logg('Neues DBObjekt wird benutzt (id = ' . (self::$dbary[$pDBnr]->GetID()) . ')', 9);
        }
      }
    } else {
      if (self::$dbary[$pDBnr] instanceof DBOperationMySQLi) {
        if (LOG_DB) {
          Logger::logg('Vorhandenes DBObjekt wird benutzt (id = ' . self::$dbary[$pDBnr]->GetID() . ')', 9);
        }
      }
    }

    return self::$dbary[$pDBnr];
  }

  public static function getInstanceDB1($pNewInstance = false, $pReConnect = false)
  {
    return self::getInstance(1, $pNewInstance, $pReConnect);
  }

  public static function getInstanceDB2($pNewInstance = false, $pReConnect = false)
  {
    return self::getInstance(2, $pNewInstance, $pReConnect);
  }

  
  public static function reconnectDB1()
  {
    return self::getInstance(1, false, true);
  }

  
  public static function reconnectDB2()
  {
    return self::getInstance(2, false, true);
  }

  
  public static function getNewDBOperation($connectionInfo, $pDBnr)
  {
    $tmp = null;

    if ($pDBnr == 1) {
      $tmp = self::getNewDBOperationByDBType($connectionInfo, $pDBnr, $connectionInfo->db_typ);
      if (!$tmp) {
        die('Fehler in der Datenbank-Konfiguration');
      }
    } elseif ($pDBnr == 2) {
      $tmp = self::getNewDBOperationByDBType($connectionInfo, $pDBnr, $connectionInfo->db2_typ);
    } else {
      Logger::logg('DBNummer: ' . $pDBnr . ' entspricht keiner gültigen Datenbanknummer.', 50);

      return false;
    }

    return $tmp;
  }

  
  private static function getNewDBOperationByDBType($pConnectionInfo, $pDBnr, $pDBType)
  {
    switch ($pDBType) {
      case DBTypeEnum::MYSQLI:
        $tmp = new DBOperationMySQLi($pConnectionInfo, $pDBnr);
        break;
      case DBTypeEnum::SQLSRV:
        $tmp = new DBOperationMssqlSqlsrv($pConnectionInfo, $pDBnr);
        break;
      case DBTypeEnum::MSSQLODBC:
        $tmp = new DBOperationMSSQLODBC($pConnectionInfo, $pDBnr);
        break;
      case DBTypeEnum::ORACLE:
        $tmp = new DBOperationOracle($pConnectionInfo, $pDBnr);
        break;
      case DBTypeEnum::MSSQL: 
      case DBTypeEnum::MYSQL: 
      case DBTypeEnum::NODB:
      default:
        Logger::logg('Es kann kein DBOperationTyp instantiiert werden, da der DBTyp NODB ist / nicht hinterlegt wurde.', 50);
        $tmp = false;
        break;
    }

    return $tmp;
  }

  
  abstract public function connect_db($p_dbserver, $p_dbname, $p_dbuser, $p_dbpass);

  
  public function clear()
  {
    $this->resultset = -1;
    $this->actualrow = -1;
    $this->dbparser = new parseSQL();
    $this->dbparser->servertyp = $this->getDBName();
  }

  
  abstract public function getLastID($tablename);

  
  public function getNextIDGen($pTablename, $columnname)
  {
    $tablename = $this->getString4Sql($pTablename);
    $this->clear();
    
    $this->execute('INSERT INTO gen_' . $tablename . ' (dummy_wert) VALUES (0)');
    $tmpid = $this->getLastID('gen_' . $tablename);
    $this->clear();
    $this->execute('DELETE FROM gen_' . $tablename
          . " WHERE gen_ID = '" . $tmpid . "'");
    $this->clear();

    return $tmpid;
  }

  
  public function getNextIdGenWithoutCorrespondingTable($pTablename, $columnname)
  {
    return $this->getNextIDGen($pTablename, $columnname);
  }

  
  abstract public function query($sql);

  
  abstract public function execute($sql);

  
  abstract public function rowcount();

  
  abstract public function selectrow($rownr);

  
  public function selectNextRow(): bool
  {
    if (++$this->actualrow < $this->rowcount()) { 
      $this->selectrow($this->actualrow);

      return true;
    } else {
      return false;
    }
  }

  
  public function resetToFirstRow(): void
  {
    if ($this->actualrow !== -1) {
      $this->actualrow = -1;
      $this->selectrow(0);
    }
  }

  
  abstract public function fieldbyname($name);

  
  abstract public function fieldExists($name);

  
  abstract public function getDBName();

  
  public function throwCriticalError($pErrorMsg, $sql = '')
  {
    $errorFromDb = $this->getError();
    DebugOperations::writeLogDatei($pErrorMsg . $sql . "\n" . $errorFromDb);
    $this->weitereFehlerbehandlung($pErrorMsg, $sql);
    DebugOperations::sendSqlErrorMail($pErrorMsg . "\n" . $errorFromDb, $sql, $this->getDBName());
    DebugOperations::showUserMessageAndDie();
  }

  
  protected function weitereFehlerbehandlung($pErrorMsg, $sql)
  {
  }

  
  public function getError()
  {
  }

  
  public function subselectPossible()
  {
    return true;
  }

  
  public function multiValueInsertPossible()
  {
    return false;
  }

  
  abstract public function beginTransaction(): bool;

  
  abstract public function commitTransaction(): bool;

  
  abstract public function rollbackTransaction(): bool;

  
  abstract public function closeConnection();

  
  public function checkConnection($pCheckTables = true)
  {
    return false;
  }

  protected static $_tableExistsCache;

  
  abstract public function checkTableExists($pTableName, $pRefreshCache = false);

  
  public function openConnection()
  {
    if (    
             !$this->isConnected()
       ) {
      if (Connections::IsConnected($this->dbnr)) {
        $this->connection = &Connections::GetConnection($this->dbnr);
        if (LOG_DB) {
          Logger::logg('ALTE VERBINDUNG - ' . $this->dbnr, 9);
        }
      } else {
        switch ($this->dbnr) {
         case 1:
             $this->connect_db(
               $this->connectionInfo->db_server,
               $this->connectionInfo->db_name,
               $this->connectionInfo->db_user,
               $this->connectionInfo->db_password
             );
             if (LOG_DB) {
               Logger::logg('NEUE VERBINDUNG - 1', 9);
             }
           break;

         case 2:
             $this->connect_db(
               $this->connectionInfo->db2_server,
               $this->connectionInfo->db2_name,
               $this->connectionInfo->db2_user,
               $this->connectionInfo->db2_password
             );
             if (LOG_DB) {
               Logger::logg('NEUE VERBINDUNG - 2', 9);
             }
           break;

         default:
           if (LOG_DB) {
             Logger::logg('DBNummer: ' . $this->dbnr . ' entspricht keiner gültigen Datenbanknummer.', 50);
           }
           break;
       }
        Connections::RegisterConnection($this->dbnr, $this->connection);
      }
      if (LOG_DB) {
        Logger::logg('con: ' . StringOperations::getReadableString(print_r($this->connection, true)), 9);
      }
    }
  }

  protected function isConnected($connection = null)
  {
    $conn = $connection !== null ? $connection : $this->connection;

    return is_resource($conn);
  }

  
  public function callStoredProcedure($pSQL)
  {
    return $this->execute($pSQL);
  }

  
  public function getCount()
  {
    $return = 0;

    $this->selectrow(0);
    foreach ($this->currentRow as $value) {
      if (is_numeric($value)) {
        $return = (int)$value;
      }
      break;
    }

    return $return;
  }

  
  public function GetID()
  {
    return 0;
  }

  public function fetchArray($pColumNames = array(), $columnAsKeyArray = false)
  {
    $return = array();
    $count = 0;
    foreach ($pColumNames as $columnName) {
      if ($columnAsKeyArray) {
        $return[$columnName] = $this->fieldbyname($columnName);
      } else {
        $return[$count] = $this->fieldbyname($columnName);
      }
      ++$count;
    }

    return $return;
  }

  
  public function getString4Sql($pString)
  {
    return $this->dbparser->escape($pString);
  }

  public function getEnclosedString4Sql(string $str): string
  {
    return "'" . $this->getString4Sql($str) . "'";
  }

  
  public function getStrings4Sql($pArray)
  {
    $result = array();
    foreach ($pArray as $val) {
      $result[] = $this->getString4Sql($val);
    }

    return $result;
  }

  
  public function getEnclosedStrings4Sql($pArray)
  {
    $result = array();
    foreach ($pArray as $val) {
      $result[] = $this->getEnclosedString4Sql($val);
    }

    return $result;
  }

  
  public function getInt4Sql($pInt)
  {
    $default = 'keinint';
    $cleanInt = CleanOperations::cleanInt($pInt, $default);

    if ($cleanInt === $default) {
      if (PRAESENTATION) {
        return 0;
      } else {
        if (DEVELOP) {
          print_r(DebugOperations::backtrace());
        }
        Logger::logg('DBOperations::getInt4Sql: Kein Integer-Wert. GetInt4SQL: Übergebener Wert: "' . print_r($pInt, true) . '" Typ: "' . gettype($pInt) . '" Backtrace: ' . DebugOperations::backtrace(), 567);

        throw new InvalidArgumentException('DBOperations::getInt4Sql: Integerwert erwartet, jedoch "' . print_r($pInt, true) . '" übergeben');
      }
    }

    return (int)$cleanInt;
  }

  
  public function getInts4Sql($pArray)
  {
    $result = array();
    foreach ($pArray as $val) {
      $result[] = $this->getInt4Sql($val);
    }

    return $result;
  }

  public function decodeStringFromDb($value)
  {
    return $value;
  }

  public function encodeStringForDb($value)
  {
    return $value;
  }

  
  public function runTransaction($fn)
  {
    $this->beginTransaction();

    try {
      \call_user_func($fn);
      $this->commitTransaction();
    } catch (\Exception $ex) {
      $this->rollbackTransaction();

      throw $ex;
    }
  }

  public function getGuidForSql(string $guid)
  {
    $guid = $this->getString4Sql($guid);

    return $this->dbparser->prepareGuid($guid);
  }

  
  abstract public function getColumnsOfTable(string $table): array;

  
  public function getColumnsOfTables(array $tables): array
  {
    $result = array();
    foreach ($tables as $table) {
      $result[$table] = $this->getColumnsOfTable($table);
    }

    return $result;
  }

  
  public function caseInsensitiveCompOperation(string $pOperation, string $pPraefix, string $pFieldname, string $pMatch): string
  {
    return  $pPraefix . $pFieldname . ' ' . $pOperation . ' ' . $this->getEnclosedString4Sql($pMatch);
  }

  
  public function caseInsensitiveBetween(string $pPraefix, string $pFieldname, string $pLowerBound, string $pUpperBound): string
  {
    return  $pPraefix . $pFieldname . ' BETWEEN ' . $this->getEnclosedString4Sql($pLowerBound) . ' AND '
      . $this->getEnclosedString4Sql($pUpperBound);
  }

  public function caseInsensitiveLike(string $pPraefix, string $pFieldname, string $pMatch): string
  {
    return $this->caseInsensitiveCompOperation('LIKE', $pPraefix, $pFieldname, $pMatch);
  }
}