Commit | Line | Data |
---|---|---|
c1c06a6a | 1 | <?php |
a9229942 | 2 | |
c1c06a6a | 3 | namespace wcf\page; |
a9229942 | 4 | |
c1c06a6a | 5 | use wcf\data\edit\history\entry\EditHistoryEntry; |
5e40b0b2 | 6 | use wcf\data\edit\history\entry\EditHistoryEntryList; |
849204bd | 7 | use wcf\data\object\type\ObjectType; |
c1c06a6a | 8 | use wcf\data\object\type\ObjectTypeCache; |
849204bd | 9 | use wcf\system\edit\IHistorySavingObject; |
e4499881 | 10 | use wcf\system\edit\IHistorySavingObjectTypeProvider; |
c1c06a6a | 11 | use wcf\system\exception\IllegalLinkException; |
4b3690e1 | 12 | use wcf\system\request\LinkHandler; |
c1c06a6a TD |
13 | use wcf\system\WCF; |
14 | use wcf\util\Diff; | |
4b3690e1 | 15 | use wcf\util\HeaderUtil; |
c1c06a6a TD |
16 | use wcf\util\StringUtil; |
17 | ||
18 | /** | |
ac69a7b2 | 19 | * Compares two entries of the edit history. |
a9229942 | 20 | * |
ac69a7b2 MW |
21 | * @author Tim Duesterhus, Marcel Werk |
22 | * @copyright 2001-2024 WoltLab GmbH | |
23 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
c1c06a6a | 24 | */ |
a9229942 TD |
25 | class EditHistoryPage extends AbstractPage |
26 | { | |
27 | /** | |
28 | * @inheritDoc | |
29 | */ | |
30 | public $neededModules = ['MODULE_EDIT_HISTORY']; | |
31 | ||
32 | /** | |
ac69a7b2 | 33 | * List of edit history entries. |
a9229942 | 34 | */ |
ac69a7b2 | 35 | public ?EditHistoryEntryList $objectList = null; |
a9229942 TD |
36 | |
37 | /** | |
38 | * left / old version id | |
a9229942 | 39 | */ |
ac69a7b2 | 40 | public int $oldID = 0; |
a9229942 TD |
41 | |
42 | /** | |
43 | * left / old version | |
a9229942 | 44 | */ |
ac69a7b2 | 45 | public ?EditHistoryEntry $old = null; |
a9229942 TD |
46 | |
47 | /** | |
48 | * right / new version id | |
a9229942 | 49 | */ |
ac69a7b2 | 50 | public int|string $newID = 0; |
a9229942 TD |
51 | |
52 | /** | |
53 | * right / new version | |
a9229942 | 54 | */ |
ac69a7b2 | 55 | public EditHistoryEntry|IHistorySavingObject|null $new = null; |
a9229942 TD |
56 | |
57 | /** | |
58 | * differences between both versions | |
a9229942 | 59 | */ |
ac69a7b2 | 60 | public ?array $diff = null; |
a9229942 TD |
61 | |
62 | /** | |
63 | * object type of the requested object | |
a9229942 | 64 | */ |
ac69a7b2 | 65 | public ?ObjectType $objectType = null; |
a9229942 TD |
66 | |
67 | /** | |
68 | * id of the requested object | |
a9229942 | 69 | */ |
ac69a7b2 | 70 | public int $objectID = 0; |
a9229942 TD |
71 | |
72 | /** | |
73 | * requested object | |
a9229942 | 74 | */ |
ac69a7b2 | 75 | public ?IHistorySavingObject $object = null; |
a9229942 | 76 | |
5b7583d4 MW |
77 | /** |
78 | * Rendering mode (html or raw). | |
79 | */ | |
80 | public string $mode = 'html'; | |
81 | ||
ac69a7b2 MW |
82 | #[\Override] |
83 | public function readParameters(): void | |
a9229942 TD |
84 | { |
85 | parent::readParameters(); | |
86 | ||
87 | if (isset($_REQUEST['oldID'])) { | |
88 | $this->oldID = \intval($_REQUEST['oldID']); | |
89 | $this->old = new EditHistoryEntry($this->oldID); | |
90 | if (!$this->old->entryID) { | |
91 | throw new IllegalLinkException(); | |
92 | } | |
93 | ||
94 | if (isset($_REQUEST['newID']) && $_REQUEST['newID'] !== 'current') { | |
95 | $this->newID = \intval($_REQUEST['newID']); | |
96 | $this->new = new EditHistoryEntry($this->newID); | |
97 | if (!$this->new->entryID) { | |
98 | throw new IllegalLinkException(); | |
99 | } | |
100 | } | |
101 | ||
102 | // if new version isn't 'current' check whether they are comparable | |
103 | if ($this->new) { | |
104 | // different objectTypes cannot be compared | |
105 | if ($this->old->objectTypeID != $this->new->objectTypeID) { | |
106 | throw new IllegalLinkException(); | |
107 | } | |
108 | // different items cannot be compared | |
109 | if ($this->old->objectID != $this->new->objectID) { | |
110 | throw new IllegalLinkException(); | |
111 | } | |
112 | } | |
113 | ||
114 | $this->objectID = $this->old->objectID; | |
115 | $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->old->objectTypeID); | |
116 | } elseif (isset($_REQUEST['objectID']) && isset($_REQUEST['objectType'])) { | |
117 | $this->objectID = \intval($_REQUEST['objectID']); | |
118 | $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName( | |
119 | 'com.woltlab.wcf.edit.historySavingObject', | |
120 | $_REQUEST['objectType'] | |
121 | ); | |
122 | } else { | |
123 | throw new IllegalLinkException(); | |
124 | } | |
125 | ||
126 | if (!$this->objectType) { | |
127 | throw new IllegalLinkException(); | |
128 | } | |
129 | ||
130 | /** @var IHistorySavingObjectTypeProvider $processor */ | |
131 | $processor = $this->objectType->getProcessor(); | |
132 | ||
a9229942 TD |
133 | $this->object = $processor->getObjectByID($this->objectID); |
134 | if (!$this->object->getObjectID()) { | |
135 | throw new IllegalLinkException(); | |
136 | } | |
137 | $processor->checkPermissions($this->object); | |
138 | $this->object->setLocation(); | |
139 | ||
140 | if (isset($_REQUEST['newID']) && !$this->new) { | |
141 | $this->new = $this->object; | |
142 | $this->newID = 'current'; | |
143 | } | |
144 | ||
5b7583d4 MW |
145 | if (isset($_REQUEST['mode']) && $_REQUEST['mode'] == 'raw') { |
146 | $this->mode = 'raw'; | |
147 | } | |
148 | ||
a9229942 TD |
149 | if (!empty($_POST)) { |
150 | HeaderUtil::redirect(LinkHandler::getInstance()->getLink('EditHistory', [ | |
151 | 'objectID' => $this->objectID, | |
152 | 'objectType' => $this->objectType->objectType, | |
153 | 'newID' => $this->newID, | |
154 | 'oldID' => $this->oldID, | |
5b7583d4 | 155 | 'mode' => $this->mode, |
a9229942 TD |
156 | ])); |
157 | ||
158 | exit; | |
159 | } | |
160 | } | |
161 | ||
ac69a7b2 MW |
162 | #[\Override] |
163 | public function readData(): void | |
a9229942 TD |
164 | { |
165 | parent::readData(); | |
166 | ||
167 | $this->objectList = new EditHistoryEntryList(); | |
168 | $this->objectList->sqlOrderBy = "time DESC, entryID DESC"; | |
169 | $this->objectList->getConditionBuilder()->add('objectTypeID = ?', [$this->objectType->objectTypeID]); | |
170 | $this->objectList->getConditionBuilder()->add('objectID = ?', [$this->objectID]); | |
171 | $this->objectList->readObjects(); | |
172 | ||
f32e8e76 MW |
173 | // set default values |
174 | if (!isset($_REQUEST['oldID']) && !isset($_REQUEST['newID'])) { | |
175 | foreach ($this->objectList as $object) { | |
176 | $this->oldID = $object->entryID; | |
177 | $this->old = $object; | |
178 | break; | |
179 | } | |
180 | $this->newID = 'current'; | |
181 | $this->new = $this->object; | |
182 | } | |
1aa45a02 | 183 | |
a9229942 TD |
184 | // valid IDs were given, calculate diff |
185 | if ($this->old && $this->new) { | |
f32e8e76 MW |
186 | $differ = Diff::getDefaultDiffer(); |
187 | ||
5028ef36 TD |
188 | $a = \explode("\n", $this->prepareMessage($this->old->getMessage())); |
189 | $b = \explode("\n", $this->prepareMessage($this->new->getMessage())); | |
1aa45a02 | 190 | $this->diff = Diff::rawDiffFromSebastianDiff($differ->diffToArray($a, $b)); |
a9229942 TD |
191 | |
192 | // create word diff for small changes (only one consecutive paragraph modified) | |
193 | for ($i = 0, $max = \count($this->diff); $i < $max;) { | |
194 | $previousIsNotRemoved = !isset($this->diff[$i - 1][0]) || $this->diff[$i - 1][0] !== Diff::REMOVED; | |
195 | $currentIsRemoved = $this->diff[$i][0] === Diff::REMOVED; | |
196 | $nextIsAdded = isset($this->diff[$i + 1][0]) && $this->diff[$i + 1][0] === Diff::ADDED; | |
197 | $afterNextIsNotAdded = !isset($this->diff[$i + 2][0]) || $this->diff[$i + 2][0] !== Diff::ADDED; | |
198 | ||
199 | if ($previousIsNotRemoved && $currentIsRemoved && $nextIsAdded && $afterNextIsNotAdded) { | |
200 | $a = \preg_split('/(\\W)/u', $this->diff[$i][1], -1, \PREG_SPLIT_DELIM_CAPTURE); | |
201 | $b = \preg_split('/(\\W)/u', $this->diff[$i + 1][1], -1, \PREG_SPLIT_DELIM_CAPTURE); | |
202 | ||
1aa45a02 | 203 | $diff = Diff::rawDiffFromSebastianDiff($differ->diffToArray($a, $b)); |
a9229942 TD |
204 | $this->diff[$i][1] = ''; |
205 | $this->diff[$i + 1][1] = ''; | |
1aa45a02 | 206 | foreach ($diff as $entry) { |
a9229942 TD |
207 | $entry[1] = StringUtil::encodeHTML($entry[1]); |
208 | ||
209 | if ($entry[0] === Diff::SAME) { | |
210 | $this->diff[$i][1] .= $entry[1]; | |
211 | $this->diff[$i + 1][1] .= $entry[1]; | |
212 | } elseif ($entry[0] === Diff::REMOVED) { | |
73384b28 | 213 | $this->diff[$i][1] .= '<del>' . $entry[1] . '</del>'; |
a9229942 | 214 | } elseif ($entry[0] === Diff::ADDED) { |
73384b28 | 215 | $this->diff[$i + 1][1] .= '<ins>' . $entry[1] . '</ins>'; |
a9229942 TD |
216 | } |
217 | } | |
218 | $i += 2; | |
219 | } else { | |
220 | $this->diff[$i][1] = StringUtil::encodeHTML($this->diff[$i][1]); | |
221 | $i++; | |
222 | } | |
223 | } | |
224 | } | |
a9229942 TD |
225 | } |
226 | ||
5028ef36 TD |
227 | private function prepareMessage(string $message): string |
228 | { | |
229 | $message = $this->formatHtml($message); | |
230 | $message = StringUtil::trim($message); | |
231 | $message = StringUtil::unifyNewlines($message); | |
232 | ||
233 | return \preg_replace('/\n{2,}/', "\n", $message); | |
234 | } | |
235 | ||
236 | private function formatHtml(string $html): string | |
237 | { | |
238 | $bothTags = ['ol', 'pre', 'table', 'tr', 'ul', 'woltlab-quote', 'woltlab-spoiler']; | |
239 | $openingTag = \implode('|', ['br', ...$bothTags]); | |
240 | $closingTag = \implode('|', ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'li', 'td', 'th', ...$bothTags]); | |
241 | ||
242 | return \preg_replace("/(<(?:{$openingTag})>|<\\/(?:{$closingTag})>)/", "\\0\n", $html); | |
243 | } | |
244 | ||
ac69a7b2 MW |
245 | #[\Override] |
246 | public function assignVariables(): void | |
a9229942 TD |
247 | { |
248 | parent::assignVariables(); | |
249 | ||
250 | WCF::getTPL()->assign([ | |
251 | 'oldID' => $this->oldID, | |
252 | 'old' => $this->old, | |
253 | 'newID' => $this->newID, | |
254 | 'new' => $this->new, | |
255 | 'object' => $this->object, | |
256 | 'diff' => $this->diff, | |
257 | 'objects' => $this->objectList, | |
258 | 'objectID' => $this->objectID, | |
259 | 'objectType' => $this->objectType, | |
5b7583d4 | 260 | 'mode' => $this->mode, |
a9229942 TD |
261 | ]); |
262 | } | |
c1c06a6a | 263 | } |