5 use wcf\data\edit\history\entry\EditHistoryEntry
;
6 use wcf\data\edit\history\entry\EditHistoryEntryList
;
7 use wcf\data\
object\type\ObjectType
;
8 use wcf\data\
object\type\ObjectTypeCache
;
9 use wcf\system\edit\IHistorySavingObject
;
10 use wcf\system\edit\IHistorySavingObjectTypeProvider
;
11 use wcf\system\exception\IllegalLinkException
;
12 use wcf\system\request\LinkHandler
;
15 use wcf\util\HeaderUtil
;
16 use wcf\util\StringUtil
;
19 * Compares two entries of the edit history.
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>
25 class EditHistoryPage
extends AbstractPage
30 public $neededModules = ['MODULE_EDIT_HISTORY'];
33 * List of edit history entries.
35 public ?EditHistoryEntryList
$objectList = null;
38 * left / old version id
40 public int $oldID = 0;
45 public ?EditHistoryEntry
$old = null;
48 * right / new version id
50 public int|
string $newID = 0;
55 public EditHistoryEntry|IHistorySavingObject|
null $new = null;
58 * differences between both versions
60 public ?
array $diff = null;
63 * object type of the requested object
65 public ?ObjectType
$objectType = null;
68 * id of the requested object
70 public int $objectID = 0;
75 public ?IHistorySavingObject
$object = null;
78 * Rendering mode (html or raw).
80 public string $mode = 'html';
83 public function readParameters(): void
85 parent
::readParameters();
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();
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();
102 // if new version isn't 'current' check whether they are comparable
104 // different objectTypes cannot be compared
105 if ($this->old
->objectTypeID
!= $this->new->objectTypeID
) {
106 throw new IllegalLinkException();
108 // different items cannot be compared
109 if ($this->old
->objectID
!= $this->new->objectID
) {
110 throw new IllegalLinkException();
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']
123 throw new IllegalLinkException();
126 if (!$this->objectType
) {
127 throw new IllegalLinkException();
130 /** @var IHistorySavingObjectTypeProvider $processor */
131 $processor = $this->objectType
->getProcessor();
133 $this->object = $processor->getObjectByID($this->objectID
);
134 if (!$this->object->getObjectID()) {
135 throw new IllegalLinkException();
137 $processor->checkPermissions($this->object);
138 $this->object->setLocation();
140 if (isset($_REQUEST['newID']) && !$this->new) {
141 $this->new = $this->object;
142 $this->newID
= 'current';
145 if (isset($_REQUEST['mode']) && $_REQUEST['mode'] == 'raw') {
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
,
155 'mode' => $this->mode
,
163 public function readData(): void
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();
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;
180 $this->newID
= 'current';
181 $this->new = $this->object;
184 // valid IDs were given, calculate diff
185 if ($this->old
&& $this->new) {
186 $differ = Diff
::getDefaultDiffer();
188 $a = \
explode("\n", $this->prepareMessage($this->old
->getMessage()));
189 $b = \
explode("\n", $this->prepareMessage($this->new->getMessage()));
190 $this->diff
= Diff
::rawDiffFromSebastianDiff($differ->diffToArray($a, $b));
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
;
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
);
203 $diff = Diff
::rawDiffFromSebastianDiff($differ->diffToArray($a, $b));
204 $this->diff
[$i][1] = '';
205 $this->diff
[$i +
1][1] = '';
206 foreach ($diff as $entry) {
207 $entry[1] = StringUtil
::encodeHTML($entry[1]);
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
) {
213 $this->diff
[$i][1] .= '<del>' . $entry[1] . '</del>';
214 } elseif ($entry[0] === Diff
::ADDED
) {
215 $this->diff
[$i +
1][1] .= '<ins>' . $entry[1] . '</ins>';
220 $this->diff
[$i][1] = StringUtil
::encodeHTML($this->diff
[$i][1]);
227 private function prepareMessage(string $message): string
229 $message = $this->formatHtml($message);
230 $message = StringUtil
::trim($message);
231 $message = StringUtil
::unifyNewlines($message);
233 return \
preg_replace('/\n{2,}/', "\n", $message);
236 private function formatHtml(string $html): string
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]);
242 return \
preg_replace("/(<(?:{$openingTag})>|<\\/(?:{$closingTag})>)/", "\\0\n", $html);
246 public function assignVariables(): void
248 parent
::assignVariables();
250 WCF
::getTPL()->assign([
251 'oldID' => $this->oldID
,
253 'newID' => $this->newID
,
255 'object' => $this->object,
256 'diff' => $this->diff
,
257 'objects' => $this->objectList
,
258 'objectID' => $this->objectID
,
259 'objectType' => $this->objectType
,
260 'mode' => $this->mode
,