Backend für Dokumentenverwaltung (#16)

parent 1f31fe35
......@@ -140,7 +140,7 @@
protected $path = '/secdoc/';
/** @var int Aktuelle DB-Version */
const DBVERSION = 10; # Version für eine DB-Struktur; zur Überprüfung beim Laden genutzt
const DBVERSION = 11; # Version für eine DB-Struktur; zur Überprüfung beim Laden genutzt
/** @var int Maximale Anzahl an Historien-Einträgen */
const MAXHISTORY = 15; # Nur diese Anzahl an Historien-Einträgen wird behalten
......@@ -248,6 +248,14 @@
Delimit TEXT, -- Abgrenzung
URL TEXT DEFAULT '', -- URL
PRIMARY KEY (Identifier)
);",
"CREATE TABLE documents ( -- Zuweisung von Dokumenten zu Dokumentationen
DocID INTEGER PRIMARY KEY AUTOINCREMENT, -- ID des Dokuments
ProcessID INT NOT NULL, -- ID der Dokumentation
Description TEXT NOT NULL DEFAULT '', -- Beschreibung
FileRef TEXT NOT NULL, -- Verweis auf Datei
Date DATE NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')), -- Letztes Änderungsdatum
FOREIGN KEY (ProcessID) REFERENCES verfahren(ID) ON UPDATE CASCADE ON DELETE CASCADE
);"
];
......@@ -382,6 +390,15 @@
Delimit TEXT, -- Abgrenzung
URL TEXT DEFAULT '', -- URL
PRIMARY KEY (Identifier)
);",
# 17
"CREATE TABLE documents ( -- Zuweisung von Dokumenten zu Dokumentationen
DocID INTEGER PRIMARY KEY AUTOINCREMENT, -- ID des Dokuments
ProcessID INT NOT NULL, -- ID der Dokumentation
Description TEXT NOT NULL DEFAULT '', -- Beschreibung
FileRef TEXT NOT NULL, -- Verweis auf Datei
Date DATE NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')), -- Letztes Änderungsdatum
FOREIGN KEY (ProcessID) REFERENCES verfahren(ID) ON UPDATE CASCADE ON DELETE CASCADE
);"
];
......@@ -582,6 +599,15 @@
$this->pdo->exec("PRAGMA user_version = 10;");
$this->pdo->commit();
}
else if($db_version == 10) {
trigger_error("[SecDoc] DBCon.class.php -> Aktualisiere Datenbank von Version $db_version zu " . self::DBVERSION . "!");
error_log("[SecDoc] DBCon.class.php -> Aktualisiere Datenbank von Version $db_version zu " . self::DBVERSION . "!");
$this->pdo->beginTransaction();
$this->pdo->exec(self::TABLES[17]);
$this->pdo->exec("PRAGMA user_version = 11;");
$this->pdo->commit();
}
else {
throw new Exception("Datenbank kann nicht genutzt werden, da die Version nicht übereinstimmt und nicht aktualisiert werden kann! (Ist: $db_version - Soll: " . self::DBVERSION . ")");
}
......@@ -1674,7 +1700,7 @@
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
if(!$userIsDSB && $this->getPermissionLevel($verfahrensId, $userId, $userGroups) < 2) {
if(!$userIsDSB && $this->getPermissionLevel($verfahrensId, $userId, $userGroups) < 1) {
return FALSE;
}
......@@ -1790,6 +1816,179 @@
return $result;
}
/**
* Gibt alle Dokumente zurück, die an eine Dokumentation angehangen sind.
*
* @param int $verfahrensId ID einer Dokumentation
* @return mixed[] Array mit den Einträgen in der Form [['DocID' => 1, 'ProcessID' => 2, 'Description' => 'Test', 'FileRef' => 'test.pdf', 'Date' => '2020-09-08 12:06:18']]
* @throws PDOException
* @throws Exception
*/
public function listDocuments($verfahrensId) {
if(!$this->isConnected()) {
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
$sql = 'SELECT * FROM documents WHERE ProcessID = ? ORDER BY DocID ASC;';
$sth = $this->pdo->prepare($sql);
$sth->execute([$verfahrensId]);
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> getDocuments() Execute: $sqlDump";
return $sth->fetchAll();
}
/**
* Gibt die Detals zu einem Dokument zurück.
*
* @param int $docID ID eines Dokuments
* @return mixed[] Array mit den Einträgen in der Form ['DocID' => 1, 'ProcessID' => 2, 'Description' => 'Test', 'FileRef' => 'test.pdf', 'Date' => '2020-09-08 12:06:18']
* @throws PDOException
* @throws Exception
*/
public function getDocumentDetails($docID) {
if(!$this->isConnected()) {
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
$sql = 'SELECT * FROM documents WHERE DocID = ? LIMIT 1;';
$sth = $this->pdo->prepare($sql);
$sth->execute([$docID]);
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> getDocumentDetails() Execute: $sqlDump";
$result = $sth->fetchAll();
if(empty($result)) return [];
return $result[0];
}
/**
* Fügt ein angehängtes Dokument hinzu.
*
* @param int $verfahrensId ID einer Dokumentation
* @param string $description Beschreibung des Dokuments
* @param string $fileRef Dateireferenz
* @return int ID des neuen Dokuments; -1 bei fehlenden Berechtigungen
* @throws PDOException
* @throws Exception
*/
public function addDocument($verfahrensId, $description, $fileRef) {
if(!$this->isConnected()) {
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
$sql = 'INSERT INTO documents (ProcessID, Description, FileRef) VALUES (?, ?, ?);';
$sth = $this->pdo->prepare($sql);
$sth->execute([$verfahrensId, $description, $fileRef]);
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> addDocument() Execute: $sqlDump";
$changedRows = $sth->rowCount();
if($changedRows !== 1) {
throw new Exception("DBCon.class.php -> Fehler beim Hinzufügen eines Dokuments! (Fehler: Unbekannter Fehler - Anzahl geänderter Reihen: $changedRows)");
}
# ID des neuen Dokuments holen
$sth = $this->pdo->prepare('SELECT last_insert_rowid();');
$sth->execute();
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> addDocument() Execute: $sqlDump";
$docID = $sth->fetch()['last_insert_rowid()'];
if($docID === FALSE || !is_numeric($docID)) {
throw new Exception("DBCon.class.php -> Fehler beim Hinzufügen eines Dokuments! (Fehler: ID des neuen Dokuments konnte nicht abgefragt werden (Wert: $docID))");
}
return $docID;
}
/**
* Aktualisiert ein angehängtes Dokument.
*
* @param int $docID ID eines Dokuments
* @param string $description Neue Beschreibung des Dokuments
* @param string $fileRef Neue Dateireferenz
* @return bool TRUE bei Erfolg, sonst FALSE
* @throws PDOException
* @throws Exception
*/
public function updateDocument($docID, $description, $fileRef) {
if(!$this->isConnected()) {
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
$sql = 'UPDATE documents SET Description = ?, FileRef = ?, Date = (datetime(CURRENT_TIMESTAMP, \'localtime\')) WHERE DocID = ?;';
$sth = $this->pdo->prepare($sql);
$sth->execute([$description, $fileRef, $docID]);
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> updateDocument() Execute: $sqlDump";
$changedRows = $sth->rowCount();
if($changedRows !== 1) {
throw new Exception("DBCon.class.php -> Fehler beim Aktualisieren eines Dokuments! (Fehler: Unbekannter Fehler - Anzahl geänderter Reihen: $changedRows)");
}
return TRUE;
}
/**
* Löscht ein angehängtes Dokument.
*
* @param int $docID ID eines Dokuments
* @return bool TRUE bei Erfolg, sonst FALSE
* @throws PDOException
* @throws Exception
*/
public function deleteDocument($docID) {
if(!$this->isConnected()) {
throw new Exception("DBCon.class.php -> Keine aktive Datenbank-Verbindung!");
}
$sql = 'DELETE FROM documents WHERE DocID = ?;';
$sth = $this->pdo->prepare($sql);
$sth->execute([$docID]);
ob_start();
$sth->debugDumpParams();
$sqlDump = ob_get_clean();
print "DBCon.class.php -> updateDocument() Execute: $sqlDump";
$changedRows = $sth->rowCount();
if($changedRows !== 1) {
throw new Exception("DBCon.class.php -> Fehler beim Aktualisieren eines Dokuments! (Fehler: Unbekannter Fehler - Anzahl geänderter Reihen: $changedRows)");
}
return TRUE;
}
/**
* Schreibt einen Eintrag in die Bearbeitungs-Historie.
* Sollte immer nur in Verbindung mit einer Aktion genutzt werden.
......
......@@ -21,6 +21,7 @@
require_once('DBCon.class.php');
require_once('Utils.class.php');
require_once('auth/Auth.class.php');
require_once('docmgmt/DocMGMT.class.php');
# ----------------------------------------
# Globale Konfiguration setzen
......@@ -165,11 +166,23 @@
'employee' => '(|(memberof=CN=u0mitarb,OU=Projekt-Gruppen,DC=wwu,DC=de)(memberof=CN=e0mitwwu,OU=Projekt-Gruppen,DC=wwu,DC=de))'
];
# ----------------------------------------
# Konfiguration der Dokumentenverwaltung
# ----------------------------------------
$docmgmt_method = 'local'; # Aktuell nur local unterstützt
$docmgmt_class = $docmgmt_method . 'DocMGMT';
# ----------------------------------------
# Lokale Konfiguration laden
# ----------------------------------------
include_once("$secret_dir/secdoc.conf.php");
# ----------------------------------------
# Dokumentenverwaltungsklasse laden
# ----------------------------------------
if(!@require_once("docmgmt/{$docmgmt_class}.class.php")) throw new Exception("config.inc.php Fehler: Dokumentenverwaltungsklasse '{$docmgmt_class}.class.php' wurde nicht gefunden oder konnte nicht eingebunden werden!");
$docmgmtClass = new $docmgmt_class;
# ----------------------------------------
# Authentifizierungsklasse laden
# ----------------------------------------
......
<?php
/**
* Enthält eine abstrakte Implementierung der Klasse zur Dokumentenverwaltung.
*
* @author Dustin Gawron <dustin.gawron@uni-muenster.de>
* @copyright (c) 2020 Westfälische Wilhelms-Universität Münster
* @license AGPL-3.0-or-later <https://www.gnu.org/licenses/agpl.html>
*/
/**
* Abstrakte Klasse als Vorlage zur Dokumentenverwaltung.
*
* @abstract
*/
abstract class DocMGMT {
/**
* Holt den Inhalt eines Dokuments.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @return string[] Dateiname und Dateiinhalt (base64 kodiert) (['fileName' => 'test.pdf', 'fileContent' => '...'])
*/
abstract public function getDocument($processID, $fileRef);
/**
* Fügt ein neues Dokument hinzu.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileName Dateiname
* @param string $fileContent Dateiinhalt (base64 kodiert)
* @return string Referenz zum Dokument
*/
abstract public function addDocument($processID, $fileName, $fileContent);
/**
* Aktualisiert ein Dokument.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @param string $fileName Dateiname
* @param string $fileContent Dateiinhalt (base64 kodiert)
* @return bool TRUE bei Erfolg, sonst FALSE
*/
abstract public function updateDocument($processID, $fileRef, $fileName, $fileContent);
/**
* Löscht ein Dokument.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @return bool TRUE bei Erfolg, sonst FALSE
*/
abstract public function deleteDocument($processID, $fileRef);
}
?>
<?php
/**
* Implementierung einer lokalen Dateiverwaltung.
*
* @author Dustin Gawron <dustin.gawron@uni-muenster.de>
* @copyright (c) 2020 Westfälische Wilhelms-Universität Münster
* @license AGPL-3.0-or-later <https://www.gnu.org/licenses/agpl.html>
*/
/**
* Klasse zur Implementierung einer lokalen Dateiverwaltung.
* Erweitert die Grundklasse {@link DocMGMT}.
*/
class localDocMGMT extends DocMGMT {
/**
* Holt den Inhalt eines Dokuments.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @return string[] Dateiname und Dateiinhalt (base64 kodiert) (['fileName' => 'test.pdf', 'fileContent' => '...'])
* @throws Exception
*/
public function getDocument($processID, $fileRef) {
global $pdf_dir;
$filePath = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileRef;
$returnVal = [
'fileName' => $fileRef,
'fileContent' => ''
];
if(file_exists($filePath) && is_readable($filePath)) {
$returnVal['fileContent'] = base64_encode(file_get_contents($filePath));
}
else {
trigger_error("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht gelesen werden");
error_log("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht gelesen werden");
}
return $returnVal;
}
/**
* Fügt ein neues Dokument hinzu.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileName Dateiname
* @param string $fileContent Dateiinhalt (base64 kodiert)
* @return string Referenz zum Dokument
* @throws Exception
*/
public function addDocument($processID, $fileName, $fileContent) {
global $pdf_dir;
if(!is_writable($pdf_dir)) throw new Exception("Fehlende Schreibberechtigung in '$pdf_dir'");
$processDir = $pdf_dir . DIRECTORY_SEPARATOR . $processID;
if(!file_exists($processDir)) {
if(!mkdir($processDir)) throw new Exception("Kann Verzeichnis '$processDir' nicht erstellen");
}
# Sonderzeichen entschärfen
$fileName = str_replace('.pdf', '', $fileName);
$fileName = preg_replace('/[^A-Za-z0-9_\-]/', '_', $fileName);
$fileName .= '.pdf';
$filePath = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileName;
if(file_exists($filePath)) {
$fileName = time() . $fileName;
$filePath = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileName;
}
if(file_put_contents($filePath, base64_decode($fileContent)) === FALSE) {
trigger_error("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht geschrieben werden");
error_log("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht geschrieben werden");
return '';
}
else {
return $fileName;
}
}
/**
* Aktualisiert ein Dokument.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @param string $fileName Dateiname
* @param string $fileContent Dateiinhalt (base64 kodiert)
* @return bool Neue Referenz zum Dokument
* @throws Exception
*/
public function updateDocument($processID, $fileRef, $fileName, $fileContent){
global $pdf_dir;
if(!is_writable($pdf_dir)) throw new Exception("Fehlende Schreibberechtigung in '$pdf_dir'");
$filePathOld = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileRef;
$filePathNew = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileName;
# Sonderzeichen entschärfen
$fileName = str_replace('.pdf', '', $fileName);
$fileName = preg_replace('/[^A-Za-z0-9_\-]/', '_', $fileName);
$fileName .= '.pdf';
if($filePathOld !== $filePathNew && file_exists($filePathNew)) {
$fileName = time() . $fileName;
$filePathNew = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileName;
}
if(file_put_contents($filePathNew, base64_decode($fileContent)) === FALSE) {
trigger_error("[SecDoc] localDocMGMT.php -> Datei '$filePathNew' konnte nicht geschrieben werden");
error_log("[SecDoc] localDocMGMT.php -> Datei '$filePathNew' konnte nicht geschrieben werden");
return '';
}
else {
if($filePathOld !== $filePathNew) {
if(!unlink($filePathOld)) {
trigger_error("[SecDoc] localDocMGMT.php -> Datei '$filePathOld' konnte nicht gelöscht werden");
error_log("[SecDoc] localDocMGMT.php -> Datei '$filePathOld' konnte nicht gelöscht werden");
}
}
return $fileName;
}
}
/**
* Löscht ein Dokument.
*
* @abstract
* @param int $processID ID der Dokumentation
* @param string $fileRef Dateireferenz
* @return bool TRUE bei Erfolg, sonst FALSE
* @throws Exception
*/
public function deleteDocument($processID, $fileRef) {
global $pdf_dir;
if(!is_writable($pdf_dir)) throw new Exception("Fehlende Schreibberechtigung in '$pdf_dir'");
$filePath = $pdf_dir . DIRECTORY_SEPARATOR . $processID . DIRECTORY_SEPARATOR . $fileRef;
if(unlink($filePath)) {
return TRUE;
}
else {
trigger_error("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht gelöscht werden");
error_log("[SecDoc] localDocMGMT.php -> Datei '$filePath' konnte nicht gelöscht werden");
return FALSE;
}
}
}
......@@ -1729,6 +1729,142 @@ EOH;
break;
}
case 'adddocument': {
if(empty($verfahrensId)) {
returnError('Keine ID für ein Verfahren wurde übergeben!');
}
if(empty($data) || empty($data['filename']) || empty($data['filecontent'])) {
returnError('Dateiinformationen fehlen!');
}
if(!$userIsDSB && $dbcon->getPermissionLevel($verfahrensId, $userId, $userGroups) < 2) {
returnError('Keine Schreibberechtigung für die gewählte Dokumentation!');
}
# Dateityp prüfen
$finfo = finfo_open();
if(!in_array(finfo_buffer($finfo, base64_decode($data['filecontent']), FILEINFO_MIME_TYPE), ['application/pdf'])) {
returnError('Es können nur PDF Dateien hinterlegt werden!');
}
global $docmgmtClass;
$newFileRef = $docmgmtClass->addDocument($verfahrensId, $data['filename'], $data['filecontent']);
if(empty($newFileRef)) returnError('Konnte Dokument nicht abspeichern!');
$newDocID = $dbcon->addDocument($verfahrensId, !empty($data['description']) ? $data['description'] : '', $newFileRef);
if($newDocID === -1) returnError('Konnte Document nicht in Datenbank anlegen!');
$output['success'] = TRUE;
break;
}
case 'updatedocument': {
if(empty($data) || empty($data['filename']) || empty($data['filecontent']) || empty($data['docid'])) {
returnError('Dateiinformationen fehlen!');
}
$docDetails = $dbcon->getDocumentDetails(intval($data['docid']));
if(empty($docDetails)) {
returnError('Zu aktualisierendes Dokument existiert nicht!');
}
if(!$userIsDSB && $dbcon->getPermissionLevel($docDetails['ProcessID'], $userId, $userGroups) < 2) {
returnError('Keine Schreibberechtigung für die gewählte Dokumentation!');
}
# Dateityp prüfen
$finfo = finfo_open();
if(!in_array(finfo_buffer($finfo, base64_decode($data['filecontent']), FILEINFO_MIME_TYPE), ['application/pdf'])) {
returnError('Es können nur PDF Dateien hinterlegt werden!');
}
global $docmgmtClass;
$newFileRef = $docmgmtClass->updateDocument($docDetails['ProcessID'], $docDetails['FileRef'], $data['filename'], $data['filecontent']);
if(empty($newFileRef)) returnError('Konnte Dokument nicht abspeichern!');
$newDocID = $dbcon->updateDocument($docDetails['DocID'], !empty($data['description']) ? $data['description'] : '', $newFileRef);
if($newDocID === -1) returnError('Konnte Document nicht in Datenbank aktualisieren!');
$output['success'] = TRUE;
break;
}
case 'getdocument': {
if(empty($data['docid'])) {
returnError('Dokumenten ID fehlt!');
}
$docDetails = $dbcon->getDocumentDetails(intval($data['docid']));
if(empty($docDetails)) {
returnError('Angefragtes Dokument existiert nicht!');
}
if(!$userIsDSB && $dbcon->getPermissionLevel($docDetails['ProcessID'], $userId, $userGroups) < 1) {
returnError('Keine Leseberechtigung für das angefragte Dokument!');
}
global $docmgmtClass;
$file = $docmgmtClass->getDocument($docDetails['ProcessID'], $docDetails['FileRef']);
if(empty($file) || empty($file['fileName']) || empty($file['fileContent'])) returnError('Dokument konnte nicht gelesen werden!');
$output['success'] = TRUE;
$output['data'] = $file;
break;
}
case 'deletedocument': {
if(empty($data['docid'])) {
returnError('Dokumenten ID fehlt!');
}
$docDetails = $dbcon->getDocumentDetails(intval($data['docid']));
if(empty($docDetails)) {
returnError('Angefragtes Dokument existiert nicht!');
}
if(!$userIsDSB && $dbcon->getPermissionLevel($docDetails['ProcessID'], $userId, $userGroups) < 2) {
returnError('Keine Schreibberechtigung für das angefragte Dokument!');
}
global $docmgmtClass;
$delSuccess = $docmgmtClass->deleteDocument($docDetails['ProcessID'], $docDetails['FileRef']);
if(!$delSuccess) returnError('Dokument konnte nicht gelöscht werden!');
$dbcon->deleteDocument(intval($data['docid']));
$output['success'] = TRUE;
break;
}
case 'listdocuments': {
if(empty($verfahrensId)) {
returnError('Keine ID für ein Verfahren wurde übergeben!');
}
if(!$userIsDSB && $dbcon->getPermissionLevel($verfahrensId, $userId, $userGroups) < 1) {
returnError('Keine Leseberechtigung für die angefragte Dokumentation!');
}
$documents = $dbcon->listDocuments($verfahrensId);
$output['success'] = TRUE;
$output['data'] = $documents;
$output['count'] = count($documents);
break;
}
case 'login': {
$output['data']['msg'] = 'Erfolgreich eingeloggt';
break;
......