Set default captcha type to none
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / page / EditHistoryPage.class.php
CommitLineData
c1c06a6a 1<?php
a9229942 2
c1c06a6a 3namespace wcf\page;
a9229942 4
c1c06a6a 5use wcf\data\edit\history\entry\EditHistoryEntry;
5e40b0b2 6use wcf\data\edit\history\entry\EditHistoryEntryList;
849204bd 7use wcf\data\object\type\ObjectType;
c1c06a6a 8use wcf\data\object\type\ObjectTypeCache;
849204bd 9use wcf\system\edit\IHistorySavingObject;
e4499881 10use wcf\system\edit\IHistorySavingObjectTypeProvider;
c1c06a6a 11use wcf\system\exception\IllegalLinkException;
4b3690e1 12use wcf\system\request\LinkHandler;
c1c06a6a
TD
13use wcf\system\WCF;
14use wcf\util\Diff;
4b3690e1 15use wcf\util\HeaderUtil;
c1c06a6a
TD
16use 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
25class 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}