require_once ("setting.inc.php"); require_once ("ticket.inc.php"); /** * History verwalten \n * Änderungen an Einträgen in der Datenbank protokollieren, abfragen und rückgängig machen \n * \n * Alle änderungen an der Datenbank sollen protokolliert werden, damit sie bei bedarf ungeschähen gemacht werden können * * \author Claus Muus * \date 27/03/07 */ class History { const STATUS_INACTIVE = 0; ///< der Historyeintrag ist Inaktiv const STATUS_ACTIVE = 1; ///< der Historyeintrag ist Aktiv const STATUS_ERASED = 2; ///< der Historyeintrag wurde gelöscht private static $group_name = ""; ///< Name der für alle folgenden Aktionen verwendet werden soll private static $group_time = 0; ///< Zeit der für alle folgenden Aktionen verwendet werden soll private static $group_gid = 0; ///< Gid die für alle folgenden Aktionen verwendet werden soll private static $group_level = 0; ///< Verschachtelungstiefe der Gropierung private $name = ""; ///< Name der speicher Aktionen private $time = 0; ///< Zeitpunkt der Ausfhrung (Alle zusammengehoerigen Aktionen verwenden die selbe Zeit) public $gid = 0; ///< gid der Ausfhrung (Alle zusammengehoerigen Aktionen verwenden die selbe gid) private static $checkCleanUp = false; ///< speichert ob bereits auf zu löschnede Einträge geprüft wurde /** * Initialesiert das Objekt \n * * \param name Bezeichnung der Änderung (Aktion) die durchgeführt wird */ public function __construct ($name="") { if (self::$group_level) { $this->name = self::$group_name; $this->time = self::$group_time; $this->gid = self::$group_gid; } else { $db = new Database (); $this->name = self::trimmName ($name); if ($db->query ("SELECT NOW() AS time")) { $this->time = $db['time']; } $this->gid = Setting::get ("history", "lastgid", 0) + 2; Setting::set ("history", "lastgid", $this->gid, false); } self::cleanUp (); } /** * Entfernd aus einem Namen alle für Historynamen unerlaupten Zeichen * * \param name Name der Normiert werdn soll * \return Normierter Historyname */ public static function trimmName ($name) { return preg_replace ("/^[^a-zA-Z]+|[^a-zA-Z0-9- ]/s", "", $name); } /** * Startet die Gruppierung mehrerer Änderungen */ public function groupStart () { self::$group_level ++; self::$group_name = $this->name; self::$group_time = $this->time; self::$group_gid = $this->gid; } /** * Beendet die Gruppierung mehrerer Änderungen */ public function groupEnd () { if (self::$group_level) { self::$group_level --; } } /** * eine Änderung speichern * * \param id Id des Eintrages der gespeichert werden soll * \param table name der Table in der sich der Eintrag befindet * \param set_ticket legt fest ob dem Historie Eintrag ein Ticket zugewiesen werden soll * * \return bei Erfolg ID des Historyentrages, sonst false */ public function store ($id, $table, $set_ticket=true) { $db = new Database (); if (Base::is_int ($id) && $db->query ("SELECT * FROM `{prefix}$table` WHERE id=$id")) { $values = Database::escape (serialize ($db[0])); $ticket = ($set_ticket===true) ? Ticket::id () : (Base::is_int ($set_ticket) ? $set_ticket : 0); // ReDo Eintraege loeschen if ($db->query ("SELECT h2.id, h2.table_name FROM {prefix}history h1, {prefix}history h2 WHERE h1.row_id=$id AND h1.table_name='$table' AND ($ticket=0 OR h1.ticket_id=$ticket) AND h1.status=".self::STATUS_ACTIVE." AND h2.row_id=h1.row_id AND h2.table_name=h1.table_name AND h2.ticket_id=h1.ticket_id AND h2.gid>h1.gid")) { foreach ($db->result as $row) { $db->query ("UPDATE {prefix}history SET status=".self::STATUS_ERASED." WHERE id={$row['id']}"); /* $db->query ("DELETE FROM {prefix}history WHERE id={$row['id']}"); if ($row['table_name'] == "element" && file_exists (Setting::get ("config", "filePath") . "history/" . $row['id'])) { unlink (Setting::get ("config", "filePath") . "history/" . $row['id']); } */ } } // die active Makierung loeschen $db->query ("UPDATE {prefix}history SET status=".self::STATUS_INACTIVE." WHERE row_id=$id AND table_name='$table' AND status=".self::STATUS_ACTIVE); // Werte speichern $db->query ("INSERT INTO {prefix}history (ticket_id, user_id, gid, name, time, status, table_name, row_id, row_values) VALUES ($ticket, ".User::get ('id').", {$this->gid}, '{$this->name}', '{$this->time}', '".self::STATUS_ACTIVE."', '$table', $id, '$values')"); if ($table == "element" && file_exists (Setting::get ("config", "filePath") . $id)) { if (!is_dir (Setting::get ("config", "filePath") . "history/")) { mkdir (Setting::get ("config", "filePath") . "history/"); } copy (Setting::get ("config", "filePath") . $id, Setting::get ("config", "filePath") . "history/" . $db->rowid); } return $db->rowid; } return false; } /** * Vor einer Änderung den zustand speichern, wenn kein History Eintrag existiert * * \param id Id des Eintrages der gespeichert werden soll * \param table name der Table in der sich der Eintrag befindet * \param set_ticket legt fest ob dem Historie Eintrag ein Ticket zugewiesen werden soll */ public function storeOld ($id, $table, $set_ticket=true) { $db = new Database (); if (Base::is_int ($id) && !$db->query ("SELECT id FROM {prefix}history WHERE row_id=$id AND table_name='$table' AND ticket_id=".Ticket::SERVER_ID." AND status!=".self::STATUS_ERASED)) { $old_name = $this->name; $this->name = "$table no changes"; $this->gid--; $this->store ($id, $table, $set_ticket); $this->gid++; $this->name = $old_name; } } /** * Anfangs History erstellen * * \param table name der Table in der sich der Eintrag befindet */ public static function create ($table) { $db = new Database (); $db->query ("DELETE FROM {prefix}history WHERE table_name='$table'"); $history = new History ("$table old value"); if ($db->query ("SELECT id, ticket_id FROM `{prefix}$table`")) { foreach ($db->result as $row) { $history->store ($row['id'], $table, $row['ticket_id']); $history->gid++; } } Setting::set ("history", "lastgid", $history->gid, false); } /** * History einträge löschen * * \param table name der Table in der sich der Eintrag befindet */ public static function del ($table) { $db = new Database (); $db->query ("DELETE FROM {prefix}history WHERE table_name='$table'"); } /** * Liefert daten zu einem Historyeintrag * * \param id ID des History Eintrages * * \return Array der Änderung (id, name, time, table_name, row_id) */ public static function get ($id) { $db = new Database (); if (Base::is_int ($id) && $db->query ("SELECT id, name, time, table_name, row_id FROM {prefix}history WHERE id=$id")) { return $db[0]; } return array (); } /** * Liste der History Einträge * * \param id Id des Eintrages/Daten zu den die Änderungen gesucht werden * \param table Tabelle aus der die id stammt, von der also ne History gesucht wird * \param active_ticket bestimmt ob die History nur zum aktiven Ticket gesucht werden soll * \param limit maximale Anzahl an undo und redo ergebnisen (jeweils) * \param action_name nur nach diesen aktionen suchen (z.B. 'group del') * * \return Array der Änderungen (id, name, time, status) */ public static function getList ($id=0, $table="", $active_ticket=true, $limit=0, $action_name="") { $db = new Database (); $changes = array (); $ticket = ($active_ticket===true) ? Ticket::id () : (Base::is_int ($active_ticket) ? $active_ticket : 0); if (is_array ($table)) { $table = "'".implode ("','", $table)."'"; } else { $table = "'$table'"; } if ($limit) { if (Base::is_int ($id) && $db->query ("SELECT id, user_id, gid, name, time, status, table_name, row_id FROM {prefix}history WHERE ($id=0 OR row_id=$id) AND ('$action_name'='' OR name='$action_name') AND ($table='' OR table_name IN ($table)) AND ($ticket=0 OR ticket_id=".Ticket::SERVER_ID." OR ticket_id=$ticket) AND status='".self::STATUS_ACTIVE."' GROUP BY time, name ORDER BY time, id")) { $changes = $db->result; if (!$action_name) { $firstGid = $db['gid']; $lastGid = $changes[count ($changes)-1]['gid']; if ($db->query ("SELECT id, user_id, name, time, status, table_name, row_id FROM {prefix}history WHERE ($id=0 OR row_id=$id) AND ($table='' OR table_name IN ($table)) AND ($ticket=0 OR ticket_id=".Ticket::SERVER_ID." OR ticket_id=$ticket) AND gid<$firstGid GROUP BY time, name ORDER BY time DESC, id DESC LIMIT 0,$limit")) { $changes = array_merge (array_reverse ($db->result), $changes); } if ($db->query ("SELECT id, user_id, name, time, status, table_name, row_id FROM {prefix}history WHERE ($id=0 OR row_id=$id) AND ($table='' OR table_name IN ($table)) AND ($ticket=0 OR ticket_id=".Ticket::SERVER_ID." OR ticket_id=$ticket) AND gid>$lastGid GROUP BY time, name ORDER BY time, id LIMIT 0,$limit")) { $changes = array_merge ($changes, $db->result); } } } } else { if (Base::is_int ($id) && $db->query ("SELECT id, user_id, name, time, status, table_name, row_id FROM {prefix}history WHERE ($id=0 OR row_id=$id) AND ('$action_name'='' OR name='$action_name') AND ($table='' OR table_name IN ($table)) AND ($ticket=0 OR ticket_id=".Ticket::SERVER_ID." OR ticket_id=$ticket) GROUP BY time, name ORDER BY time, id")) { $changes = $db->result; } } return $changes; } /** * Liefert den ersten Undo Eintrag * * \param ids Ids der Eintraege/Daten zu dem die Änderung gesucht wird * \param table Tabelle aus der die id stammt, von der also ne History gesucht wird * \param active_ticket bestimmt ob nur in der History zum aktiven Ticket gesucht werden soll * * \return Array der Änderung (id, name, time, user_id) */ public static function getFirstUndo ($ids, $table, $active_ticket=true) { $db = new Database (); $ticket = ($active_ticket===true) ? Ticket::id () : (Base::is_int ($active_ticket) ? $active_ticket : 0); if (is_array ($ids)) { $ids = implode (",", $ids); } if ($ids && $db->query ("SELECT h2.id, h2.name, h2.time, h2.user_id FROM {prefix}history h1, {prefix}history h2 WHERE h1.row_id IN ($ids) AND h1.table_name='$table' AND ($ticket=0 OR h1.ticket_id=".Ticket::SERVER_ID." OR h1.ticket_id=$ticket) AND h1.status='".self::STATUS_ACTIVE."' AND h2.row_id=h1.row_id AND h2.table_name=h1.table_name AND ($ticket=0 OR h2.ticket_id=".Ticket::SERVER_ID." OR h2.ticket_id=$ticket) AND h2.status='".self::STATUS_INACTIVE."' AND h2.gid