Merge pull request #5987 from WoltLab/acp-dahsboard-box-hight
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / edit / EditHistoryManager.class.php
CommitLineData
fb5d4992 1<?php
a9229942 2
fb5d4992 3namespace wcf\system\edit;
a9229942 4
f86d7ff7 5use wcf\data\edit\history\entry\EditHistoryEntryList;
fb5d4992 6use wcf\data\object\type\ObjectTypeCache;
9d3b749f 7use wcf\system\database\util\PreparedStatementConditionBuilder;
fb5d4992
TD
8use wcf\system\exception\SystemException;
9use wcf\system\SingletonFactory;
10use 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
19class 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}