Commit | Line | Data |
---|---|---|
fb5d4992 | 1 | <?php |
a9229942 | 2 | |
fb5d4992 | 3 | namespace wcf\system\edit; |
a9229942 | 4 | |
f86d7ff7 | 5 | use wcf\data\edit\history\entry\EditHistoryEntryList; |
fb5d4992 | 6 | use wcf\data\object\type\ObjectTypeCache; |
9d3b749f | 7 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
fb5d4992 TD |
8 | use wcf\system\exception\SystemException; |
9 | use wcf\system\SingletonFactory; | |
10 | use wcf\system\WCF; | |
11 | ||
12 | /** | |
996db334 | 13 | * Manages the edit history. |
a9229942 TD |
14 | * |
15 | * @author Tim Duesterhus | |
16 | * @copyright 2001-2019 WoltLab GmbH | |
17 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
996db334 | 18 | */ |
a9229942 TD |
19 | class EditHistoryManager extends SingletonFactory |
20 | { | |
21 | /** | |
22 | * list of available object types | |
23 | * @var array | |
24 | */ | |
25 | protected $availableObjectTypes = []; | |
26 | ||
27 | /** | |
28 | * @inheritDoc | |
29 | */ | |
30 | protected function init() | |
31 | { | |
32 | // get available object types | |
33 | $this->availableObjectTypes = ObjectTypeCache::getInstance() | |
34 | ->getObjectTypes('com.woltlab.wcf.edit.historySavingObject'); | |
35 | } | |
36 | ||
37 | /** | |
38 | * Returns the id of the object type with the given name. | |
39 | * | |
40 | * @param string $objectType | |
41 | * @return int | |
42 | * @throws SystemException | |
43 | */ | |
44 | public function getObjectTypeID($objectType) | |
45 | { | |
46 | if (!isset($this->availableObjectTypes[$objectType])) { | |
47 | throw new SystemException("unknown object type '" . $objectType . "'"); | |
48 | } | |
49 | ||
50 | return $this->availableObjectTypes[$objectType]->objectTypeID; | |
51 | } | |
52 | ||
53 | /** | |
54 | * Adds a new entry. | |
55 | * | |
56 | * @param string $objectType | |
57 | * @param int $objectID | |
58 | * @param string $message | |
59 | * @param int $time | |
60 | * @param int $userID | |
61 | * @param string $username | |
62 | * @param string $editReason | |
63 | * @param int $obsoletedByUserID The userID of the user that forced this entry to become outdated | |
64 | */ | |
65 | public function add($objectType, $objectID, $message, $time, $userID, $username, $editReason, $obsoletedByUserID) | |
66 | { | |
67 | // no op, if edit history is disabled | |
68 | if (!MODULE_EDIT_HISTORY) { | |
69 | return; | |
70 | } | |
71 | ||
72 | // save new entry | |
73 | $sql = "INSERT INTO wcf" . WCF_N . "_edit_history_entry | |
74 | (objectTypeID, objectID, message, time, obsoletedAt, userID, username, editReason, obsoletedByUserID) | |
75 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; | |
76 | $statement = WCF::getDB()->prepareStatement($sql); | |
77 | $statement->execute([ | |
78 | $this->getObjectTypeID($objectType), | |
79 | $objectID, | |
80 | $message, | |
81 | $time, | |
82 | TIME_NOW, | |
83 | $userID, | |
84 | $username, | |
85 | $editReason, | |
86 | $obsoletedByUserID, | |
87 | ]); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Deletes edit history entries. | |
92 | * | |
93 | * @param string $objectType | |
94 | * @param int[] $objectIDs | |
95 | */ | |
96 | public function delete($objectType, array $objectIDs) | |
97 | { | |
98 | $objectTypeID = $this->getObjectTypeID($objectType); | |
99 | ||
100 | $itemsPerLoop = 1000; | |
101 | $loopCount = \ceil(\count($objectIDs) / $itemsPerLoop); | |
102 | ||
103 | WCF::getDB()->beginTransaction(); | |
104 | for ($i = 0; $i < $loopCount; $i++) { | |
105 | $batchObjectIDs = \array_slice($objectIDs, $i * $itemsPerLoop, $itemsPerLoop); | |
106 | ||
107 | $conditionBuilder = new PreparedStatementConditionBuilder(); | |
108 | $conditionBuilder->add('objectTypeID = ?', [$objectTypeID]); | |
109 | $conditionBuilder->add('objectID IN (?)', [$batchObjectIDs]); | |
110 | ||
111 | $sql = "DELETE FROM wcf" . WCF_N . "_edit_history_entry | |
112 | " . $conditionBuilder; | |
113 | $statement = WCF::getDB()->prepareStatement($sql); | |
114 | $statement->execute($conditionBuilder->getParameters()); | |
115 | } | |
116 | WCF::getDB()->commitTransaction(); | |
117 | } | |
118 | ||
119 | /** | |
120 | * Performs mass reverting of edits by the given users in the given timeframe. | |
121 | * | |
122 | * @param int[] $userIDs | |
123 | * @param int $timeframe | |
124 | */ | |
125 | public function bulkRevert(array $userIDs, $timeframe = 86400) | |
126 | { | |
127 | if (empty($userIDs)) { | |
128 | return; | |
129 | } | |
130 | ||
131 | // 1: Select the newest edit history item for each object ("newestEntries") | |
132 | // 2: Check whether the edit was made by the offending users ("vandalizedEntries") | |
133 | // 3: Fetch the newest version that is either: | |
134 | // a) older than $timeframe days | |
135 | // b) by a non offending user | |
136 | $userIDPlaceholders = '?' . \str_repeat(',?', \count($userIDs) - 1); | |
137 | $sql = "SELECT MAX(entryID) | |
138 | FROM wcf" . WCF_N . "_edit_history_entry revertTo | |
139 | INNER JOIN ( | |
140 | SELECT vandalizedEntries.objectID, | |
141 | vandalizedEntries.objectTypeID | |
142 | FROM wcf" . WCF_N . "_edit_history_entry vandalizedEntries | |
143 | INNER JOIN ( | |
144 | SELECT MAX(newestEntries.entryID) AS entryID | |
145 | FROM wcf" . WCF_N . "_edit_history_entry newestEntries | |
146 | WHERE newestEntries.obsoletedAt > ? | |
147 | GROUP BY newestEntries.objectTypeID, newestEntries.objectID | |
148 | ) newestEntries2 | |
149 | WHERE newestEntries2.entryID = vandalizedEntries.entryID | |
150 | AND vandalizedEntries.obsoletedByUserID IN (" . $userIDPlaceholders . ") | |
151 | ) AS vandalizedEntries2 | |
152 | WHERE revertTo.objectID = vandalizedEntries2.objectID | |
153 | AND revertTo.objectTypeID = vandalizedEntries2.objectTypeID | |
154 | AND ( | |
155 | revertTo.obsoletedAt <= ? | |
156 | OR revertTo.time <= ? | |
157 | OR revertTo.userID NOT IN(" . $userIDPlaceholders . ") | |
158 | ) | |
159 | GROUP BY revertTo.objectTypeID, revertTo.objectID"; | |
160 | $statement = WCF::getDB()->prepareStatement($sql); | |
161 | $statement->execute(\array_merge( | |
162 | [TIME_NOW - $timeframe], | |
163 | $userIDs, | |
164 | [TIME_NOW - $timeframe], | |
165 | [TIME_NOW - $timeframe], | |
166 | $userIDs | |
167 | )); | |
168 | ||
169 | $entryIDs = $statement->fetchAll(\PDO::FETCH_COLUMN); | |
170 | if (empty($entryIDs)) { | |
171 | return; | |
172 | } | |
173 | ||
174 | $list = new EditHistoryEntryList(); | |
175 | $list->getConditionBuilder()->add('entryID IN(?)', [$entryIDs]); | |
176 | $list->readObjects(); | |
177 | foreach ($list as $entry) { | |
178 | $entry->getObject()->revertVersion($entry); | |
179 | } | |
180 | } | |
fb5d4992 | 181 | } |