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.gidquery ("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>h1.gid ORDER BY h2.time, h2.id, h1.time, h1.id LIMIT 0,1")) { return $db[0]; } else { return array (); } } /** * Liefert den aktieven Eintrag * * \param id Id des Eintrages/Daten zu den der Aktive Historyeintrag gesucht wird * \param table Tabelle aus der die id stammt, von der also der aktive Historyeintrag gesucht wird * * \return Array der Änderung (id, name, time) */ /* public function getActiv ($id, $table) { if (Base::is_int ($id) && $db->query ("SELECT id, name, time, user_id FROM {prefix}history WHERE row_id=$id AND table_name='$table' AND (ticket_id=".Ticket::SERVER_ID." OR ticket_id=".Ticket::id ().") AND status='".self::STATUS_ACTIVE."' GROUP BY time, name ORDER BY time, id LIMIT 0,1")) { return $db[0]; } } */ /** * Undo Einträge zu einem bestimmten History Eintrag (z.B. für History Liste zu gelöschen Einträgen) * * \param id Id des History Eintrages zu dem die Änderung gesucht wird * * \return Array der Änderung (id, name, time) */ public static function getUndo ($id) { $db = new Database (); $changes = array (); if (Base::is_int ($id) && $db->query ("SELECT h2.id, CONCAT(h1.name, ' undo') as name, h2.time, h2.status, h2.gid FROM {prefix}history h1, {prefix}history h2 WHERE h1.id=$id AND h2.id!=h1.id AND h2.row_id=h1.row_id AND h2.table_name=h1.table_name AND h2.ticket_id=h1.ticket_id AND h2.gidquery ("SELECT h2.id, CONCAT(h1.name, ' redo') as name, h2.time, h2.status, h2.gid FROM {prefix}history h1, {prefix}history h2 WHERE h1.id=$id AND h2.id!=h1.id 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 GROUP BY time, name ORDER BY gid ASC, id ASC LIMIT 0,1")) { $changes = $db[0]; } return $changes; } /** * Einen älteren History Stand wieder herstellen * * \param id Id des History Eintrages der wieder hergestellt werden soll */ public static function restore ($id) { $db = new Database (); if (Base::is_int ($id) && $db->query ("SELECT h2.id, h2.ticket_id, h2.table_name, h2.row_id, h2.row_values FROM {prefix}history h1, {prefix}history h2 WHERE h1.id=$id AND h2.name=h1.name AND h2.time=h1.time")) { foreach ($db->result as $row) { $id = $row['id']; $ticket = $row['ticket_id']; $table = $row['table_name']; $row_id = $row['row_id']; $values = Database::escape (unserialize ($row['row_values'])); foreach ($values as $key => $value) { $values[$key] = "$key='$value'"; } $values = implode(", ", $values); $db->query ("UPDATE `{prefix}$table` SET $values WHERE id='$row_id'"); if ($table == "element" && file_exists (Setting::get ("config", "filePath") . "history/" . $id)) { copy (Setting::get ("config", "filePath") . "history/" . $id, Setting::get ("config", "filePath") . $row_id); } // den activen history Eintrag neu setzen $db->query ("UPDATE {prefix}history SET status=".self::STATUS_INACTIVE." WHERE row_id=$row_id AND table_name='$table' AND ticket_id=$ticket AND status=".self::STATUS_ACTIVE); $db->query ("UPDATE {prefix}history SET status=".self::STATUS_ACTIVE." WHERE id=$id"); } } } /** * Alte History Einträge löschen (und die entsprechenden Einträge in den anderen Tabellen) * * \param forceCheck Check durchführen, auch wenn der bereits durchgeführt wurde */ public static function cleanUp ($forceCheck=false) { if (User::get ('id') && (!self::$checkCleanUp || $forceCheck)) { self::$checkCleanUp = true; $lastcheck = Setting::get ("history", "last clean up check"); if ($lastcheck < date ("Y-m-d") || $forceCheck) { // nur einmal täglich Setting::set ("history", "last clean up check", date ("Y-m-d H:i:s"), false); $historyTime = Setting::get ("system", "historyTime"); $deleteOldOffline = Setting::get ("system", "deleteOldOffline"); $db = new Database (); if (Base::is_int ($historyTime)) { // Datein von Elementen zu abgelaufenen History Einträgen löschen if ($db->query ("SELECT id FROM {prefix}history WHERE time<(NOW() - INTERVAL $historyTime DAY) AND table_name='element'")) { foreach ($db->result as $row) { if (file_exists (Setting::get ("config", "filePath") . "history/" . $row['id'])) { if (!@unlink (Setting::get ("config", "filePath") . "history/" . $row['id'])) { $messages = Setting::get ("system", "errors"); $messages.= I18n::replace (I18n::tr ("history/message", "can not delete file '[file]'"), array ('file'=>Setting::get ("config", "filePathRel")."history/".$row['id']))."\n"; Setting::set ("system", "errors", $messages, false); } } } } // Abgelaufene History Einträgen löschen $db->query ("DELETE FROM {prefix}history WHERE time<(NOW() - INTERVAL $historyTime DAY)"); // Gelöschte und offline Einträge die keine History mehr haben foreach (array ('config', 'element', 'group', 'i18n', 'ticket', 'timer', 'user') as $table) { if ($db->query ("SELECT t.id FROM `{prefix}$table` t LEFT JOIN `{prefix}history` h ON h.table_name='$table' AND h.row_id=t.id WHERE h.id IS NULL AND (t.status&".BASE::STATUS_ERASED.($deleteOldOffline && ($table=="element" || $table=="group") ? " OR t.ticket_id!=0" : "").")")) { foreach ($db->result as $row) { if ($table=="element" && file_exists (Setting::get ("config", "filePath") . $row['id'])) { if (!@unlink (Setting::get ("config", "filePath") . $row['id'])) { $messages = Setting::get ("system", "errors"); $messages.= I18n::replace (I18n::tr ("history/message", "can not delete file '[file]'"), array ('file'=>Setting::get ("config", "filePathRel").$row['id']))."\n"; Setting::set ("system", "errors", $messages, false); } } if ($table != "group" || !$db->query ("SELECT id FROM `{prefix}group` WHERE online_group_id={$row['id']} AND !(status&".BASE::STATUS_ERASED.")")) { $db->query ("DELETE FROM `{prefix}$table` WHERE id={$row['id']}"); } } } } // Elemente deren Gruppe gelöscht wurde if ($db->query ("SELECT e.id FROM `{prefix}element` e LEFT JOIN `{prefix}group` g ON e.group_id=g.id WHERE g.id IS NULL")) { foreach ($db->result as $row) { if (file_exists (Setting::get ("config", "filePath") . $row['id'])) { if (!@unlink (Setting::get ("config", "filePath") . $row['id'])) { $messages = Setting::get ("system", "errors"); $messages.= I18n::replace (I18n::tr ("history/message", "can not delete file '[file]'"), array ('file'=>Setting::get ("config", "filePathRel").$row['id'])."\n"); Setting::set ("system", "errors", $messages, false); } } $db->query ("DELETE FROM `{prefix}element` WHERE id={$row['id']}"); } } // Sequenzen deren Gruppe gelöscht wurde if ($db->query ("SELECT s.id FROM `{prefix}sequence` s LEFT JOIN `{prefix}group` g ON s.id=g.sequence_id WHERE g.id IS NULL")) { foreach ($db->result as $row) { $db->query ("DELETE FROM `{prefix}sequence` WHERE id={$row['id']}"); } } } } } } /* public static function getRowValues ($id, $table) { $db = new Database (); $values = array (); if (Base::is_int ($id) && $db->query ("SELECT id, row_values FROM {prefix}history WHERE table_name='$table' AND row_id=$id")) { foreach ($db as $row) { $values[$row['id']] = unserialize ($row['row_values']); } } return $values; } public static function setRowValues ($id, $values) { $db = new Database (); $values = Database::escape (serialize ($values)); $db->query ("UPDATE {prefix}history SET row_values='$values' WHERE id=$id"); } */ } ?>