<?php

include_once(dirname(__FILE__).'/MimeTypes.php');

class File extends Model
{
  public string $FilesDir;

  function __construct(System $System)
  {
    parent::__construct($System);
    $this->FilesDir = '';
  }

  static function GetModelDesc(): ModelDesc
  {
    $Desc = new ModelDesc('File');
    $Desc->AddString('Name');
    $Desc->AddInteger('Size');
    $Desc->AddReference('Directory', FileDirectory::GetClassName(), true);
    $Desc->AddDateTime('Time');
    $Desc->AddString('Hash');
    $Desc->AddBoolean('Generate');
    return $Desc;
  }

  function Delete($Id): void
  {
    $DbResult = $this->Database->select('File', 'Name', 'Id='.$Id);
    if ($DbResult->num_rows > 0)
    {
      $DbRow = $DbResult->fetch_assoc();
      $this->Database->delete('File', 'Id='.$Id);
      unlink($this->FilesDir.'/'.$Id.'_'.$DbRow['Name']);
    }
  }

  function CreateFromUpload(string $Name): string
  {
    // Submited form with file input have to be enctype="multipart/form-data"
    $Result = 0;
    if (array_key_exists($Name, $_FILES) and ($_FILES[$Name]['name'] != ''))
    {
      if (file_exists($_FILES[$Name]['tmp_name']))
      {
        $FileName = substr($_FILES[$Name]['name'], strrpos($_FILES[$Name]['name'], '/'));
        $this->Database->query('INSERT INTO File (`Name`, `Size`) VALUES ("'.$FileName.'", '.filesize($_FILES[$Name]['tmp_name']).')');
        $InsertId = $this->Database->insert_id;
        if (move_uploaded_file($_FILES[$Name]['tmp_name'], $this->FilesDir.'/'.$InsertId.'_'.$FileName)) $Result = $InsertId;
      }
    }
    return $Result;
  }

  function DetectMimeType(string $FileName): string
  {
    $MimeTypes = GetMimeTypes();
    $MimeType = pathinfo($FileName, PATHINFO_EXTENSION);
    $Result = $MimeTypes[$MimeType][0];
    return $Result;
  }

  function DownloadHash(string $Hash): void
  {
    $this->Download('Hash="'.addslashes($Hash).'"');
  }

  function DownloadId(string $Id): void
  {
    if (!ModuleUser::Cast($this->System->GetModule('User'))->User->CheckPermission('File', 'DownloadById'))
      echo('Nemáte oprávnění');
    $this->Download('Id='.addslashes($Id));
  }

  function Download(string $Where): void
  {
    $DbResult = $this->Database->select('File', '*', $Where);
    if ($DbResult->num_rows > 0)
    {
      $DbRow = $DbResult->fetch_assoc();
      if ($DbRow['Directory'] != '') $FileName = $this->GetDir($DbRow['Directory']);
        else $FileName = $this->FilesDir;
      $FileName .= $DbRow['Name'];
      if (file_exists($FileName))
      {
        Header('Content-Type: '.$this->DetectMimeType($FileName));
        Header('Content-Disposition: inline; filename="'.$DbRow['Name'].'"');
        echo(file_get_contents($FileName));
      } else echo('Soubor nenalezen!');
    } else echo('Soubor nenalezen!');
  }

  function GetDir(string $Id): string
  {
    $DbResult = $this->Database->select('FileDirectory', '*', 'Id='.$Id);
    $DbRow = $DbResult->fetch_assoc();
    if ($DbRow['Parent'] != '') $Result = $this->GetDir($DbRow['Parent']);
      else $Result = $this->FilesDir;
    $Result .= $DbRow['Name'].'/';
    return $Result;
  }

  function GetFullPath(string $Id): string
  {
    $DbResult = $this->Database->select('File', '*', 'Id='.addslashes($Id));
    if ($DbResult->num_rows > 0)
    {
      $DbRow = $DbResult->fetch_assoc();
      if ($DbRow['Directory'] != '') $FileName = $this->GetDir($DbRow['Directory']);
        else $FileName = $this->FilesDir;
      $FileName .= $DbRow['Name'];
      return $FileName;
    } else echo('Soubor nenalezen!');
  }
}

class PageFile extends Page
{
  function Show(): string
  {
    if (array_key_exists('h', $_GET))
    {
      $Hash = $_GET['h'];
      $this->RawPage = true;
      ModuleFile::Cast($this->System->GetModule('File'))->File->DownloadHash($Hash);
      return '';
    }
    else if (array_key_exists('i', $_GET) and is_numeric($_GET['i']))
    {
      $Id = $_GET['i'] * 1;
      $this->RawPage = true;
      ModuleFile::Cast($this->System->GetModule('File'))->File->DownloadId($Id);
    }
    else return $this->SystemMessage('Chyba', 'Nezadáno id souboru');
    return '';
  }
}

class FileDirectory extends Model
{
  static function GetModelDesc(): ModelDesc
  {
    $Desc = new ModelDesc('FileDirectory');
    $Desc->AddString('Name');
    $Desc->AddReference('Parent', FileDirectory::GetClassName(), true);
    return $Desc;
  }
}

class PageFileCheck extends Page
{
  function __construct(System $System)
  {
    parent::__construct($System);
    $this->Title = 'File check';
    $this->ParentClass = 'PagePortal';
  }

  function GetAbsolutePath(string $Path): string
  {
    $Path = trim($Path);
    $IsRoot = substr($Path, 0, 1) == '/';
    $Path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $Path);
    $Parts = array_filter(explode(DIRECTORY_SEPARATOR, $Path), 'strlen');
    $Absolutes = array();
    foreach ($Parts as $Part)
    {
      if ('.' == $Part) continue;
      if ('..' == $Part)
      {
        array_pop($Absolutes);
      } else
      {
        $Absolutes[] = $Part;
      }
    }
    $Path = implode(DIRECTORY_SEPARATOR, $Absolutes);
    if ($IsRoot) $Path = DIRECTORY_SEPARATOR.$Path;
    return $Path;
  }

  function Show(): string
  {
    $Output = '<strong>Missing files:</strong><br>';
    if (!ModuleUser::Cast($this->System->GetModule('User'))->User->CheckPermission('File', 'Check'))
      return 'Nemáte oprávnění';
    $DbResult = $this->Database->select('File', 'Id');
    while ($DbRow = $DbResult->fetch_assoc())
    {
      $Id = $DbRow['Id'];
      $FileName = $this->GetAbsolutePath(ModuleFile::Cast($this->System->GetModule('File'))->File->GetFullPath($Id));
      if (!file_exists($FileName))
        $Output .= '<a href="'.$this->System->Link('/is/?a=view&amp;t=File&amp;i='.$Id).'">'.$FileName.'</a><br>';
    }
    return $Output;
  }
}

class ModuleFile extends Module
{
  public File $File;

  function __construct(System $System)
  {
    parent::__construct($System);
    $this->Name = 'File';
    $this->Version = '1.0';
    $this->Creator = 'Chronos';
    $this->License = 'GNU/GPLv3';
    $this->Description = 'Base module for file management';
    $this->Dependencies = array(ModuleUser::GetName());
    $this->Models = array(FileDirectory::GetClassName(), File::GetClassName());

    $this->File = new File($this->System);
  }

  function DoStart(): void
  {
    global $Config;

    $this->System->RegisterPage(['file'], 'PageFile');
    $this->System->RegisterPage(['file-check'], 'PageFileCheck');
    $this->File->FilesDir = dirname(__FILE__).'/../../'.$Config['Web']['UploadFileFolder'];
    $this->System->FormManager->RegisterClass('File', array(
      'Title' => 'Soubor',
      'Table' => 'File',
      'Items' => array(
        'Name' => array('Type' => 'String', 'Caption' => 'Jméno', 'Default' => ''),
        'Directory' => array('Type' => 'TDirectory', 'Caption' => 'Adresář', 'Default' => '', 'Null' => true),
        'Size' => array('Type' => 'Integer', 'Caption' => 'Velikost', 'Default' => ''),
        'Time' => array('Type' => 'DateTime', 'Caption' => 'Čas vytvoření', 'Default' => ''),
        'Hash' => array('Type' => 'String', 'Caption' => 'Haš', 'Default' => ''),
        'Invoices' => array('Type' => 'TFinanceInvoiceListFile', 'Caption' => 'Faktury', 'Default' => ''),
        'Operations' => array('Type' => 'TFinanceOperationListFile', 'Caption' => 'Operace', 'Default' => ''),
        'Contracts' => array('Type' => 'TContractListFile', 'Caption' => 'Smlouvy', 'Default' => ''),
        'StockMoves' => array('Type' => 'TStockMoveListFile', 'Caption' => 'Skladové pohyby', 'Default' => ''),
      ),
      'ItemActions' => array(
        array('Caption' => 'Stáhnout', 'URL' => '/file?i=#RowId'),
      ),
      'Actions' => array(
        array('Caption' => 'Kontrola souborů', 'URL' => '/file-check'),
      ),
    ));
    $this->System->FormManager->RegisterClass('FileDirectory', array(
      'Title' => 'Adresář souborů',
      'Table' => 'FileDirectory',
      'Items' => array(
        'Name' => array('Type' => 'String', 'Caption' => 'Jméno', 'Default' => ''),
        'Parent' => array('Type' => 'TDirectory', 'Caption' => 'Nadřazený adresář', 'Default' => '', 'Null' => true),
        'Files' => array('Type' => 'TFileList', 'Caption' => 'Soubory', 'Default' => ''),
        'Subdirectories' => array('Type' => 'TDirectoryList', 'Caption' => 'Podadresáře', 'Default' => ''),
      ),
    ));
    $this->System->FormManager->RegisterFormType('TDirectory', array(
      'Type' => 'Reference',
      'Table' => 'FileDirectory',
      'Id' => 'Id',
      'Name' => 'Name',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TFile', array(
      'Type' => 'Reference',
      'Table' => 'File',
      'Id' => 'Id',
      'Name' => 'Name',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TFileList', array(
      'Type' => 'ManyToOne',
      'Table' => 'File',
      'Id' => 'Id',
      'Ref' => 'Directory',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TDirectoryList', array(
      'Type' => 'ManyToOne',
      'Table' => 'FileDirectory',
      'Id' => 'Id',
      'Ref' => 'Parent',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TFinanceInvoiceListFile', array(
      'Type' => 'ManyToOne',
      'Table' => 'FinanceInvoice',
      'Id' => 'Id',
      'Ref' => 'File',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TFinanceOperationListFile', array(
      'Type' => 'ManyToOne',
      'Table' => 'FinanceOperation',
      'Id' => 'Id',
      'Ref' => 'File',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TContractListFile', array(
      'Type' => 'ManyToOne',
      'Table' => 'Contract',
      'Id' => 'Id',
      'Ref' => 'File',
      'Filter' => '1',
    ));
    $this->System->FormManager->RegisterFormType('TStockMoveListFile', array(
      'Type' => 'ManyToOne',
      'Table' => 'StockMove',
      'Id' => 'Id',
      'Ref' => 'File',
      'Filter' => '1',
    ));
  }

  static function Cast(Module $Module): ModuleFile
  {
    if ($Module instanceof ModuleFile)
    {
      return $Module;
    }
    throw new Exception('Expected ModuleFile type but got '.gettype($Module));
  }
}