1<?php 2 3namespace dokuwiki\ChangeLog; 4 5/** 6 * Class RevisionInfo 7 * 8 * Provides methods to show Revision Information in DokuWiki Ui components: 9 * - Ui\Recent 10 * - Ui\PageRevisions 11 * - Ui\MediaRevisions 12 * - Ui\PageDiff 13 * - Ui\MediaDiff 14 */ 15class RevisionInfo 16{ 17 public const MODE_PAGE = 'page'; 18 public const MODE_MEDIA = 'media'; 19 20 /* @var array */ 21 protected $info; 22 23 /** 24 * Constructor 25 * 26 * @param array $info Revision Information structure with entries: 27 * - date: unix timestamp 28 * - ip: IPv4 or IPv6 address 29 * - type: change type (log line type) 30 * - id: page id 31 * - user: user name 32 * - sum: edit summary (or action reason) 33 * - extra: extra data (varies by line type) 34 * - sizechange: change of filesize 35 * additionally, 36 * - current: (optional) whether current revision or not 37 * - timestamp: (optional) set only when external edits occurred 38 * - mode: (internal use) ether "media" or "page" 39 */ 40 public function __construct($info = null) 41 { 42 if (!is_array($info) || !isset($info['id'])) { 43 $info = [ 44 'mode' => self::MODE_PAGE, 45 'date' => false, 46 ]; 47 } 48 $this->info = $info; 49 } 50 51 /** 52 * Set or return whether this revision is current page or media file 53 * 54 * This method does not check exactly whether the revision is current or not. Instead, 55 * set value of associated "current" key for internal use. Some UI element like diff 56 * link button depend on relation to current page or media file. A changelog line does 57 * not indicate whether it corresponds to current page or media file. 58 * 59 * @param bool $value true if the revision is current, otherwise false 60 * @return bool 61 */ 62 public function isCurrent($value = null) 63 { 64 return (bool) $this->val('current', $value); 65 } 66 67 /** 68 * Return or set a value of associated key of revision information 69 * but does not allow to change values of existing keys 70 * 71 * @param string $key 72 * @param mixed $value 73 * @return string|null 74 */ 75 public function val($key, $value = null) 76 { 77 if (isset($value) && !array_key_exists($key, $this->info)) { 78 // setter, only for new keys 79 $this->info[$key] = $value; 80 } 81 if (array_key_exists($key, $this->info)) { 82 // getter 83 return $this->info[$key]; 84 } 85 return null; 86 } 87 88 /** 89 * Set extra key-value to the revision information 90 * but does not allow to change values of existing keys 91 * @param array $info 92 * @return void 93 */ 94 public function append(array $info) 95 { 96 foreach ($info as $key => $value) { 97 $this->val($key, $value); 98 } 99 } 100 101 102 /** 103 * file icon of the page or media file 104 * used in [Ui\recent] 105 * 106 * @return string 107 */ 108 public function showFileIcon() 109 { 110 $id = $this->val('id'); 111 if ($this->val('mode') == self::MODE_MEDIA) { 112 // media file revision 113 return media_printicon($id); 114 } elseif ($this->val('mode') == self::MODE_PAGE) { 115 // page revision 116 return '<img class="icon" src="' . DOKU_BASE . 'lib/images/fileicons/file.png" alt="' . $id . '" />'; 117 } 118 } 119 120 /** 121 * edit date and time of the page or media file 122 * used in [Ui\recent, Ui\Revisions] 123 * 124 * @param bool $checkTimestamp enable timestamp check, alter formatted string when timestamp is false 125 * @return string 126 */ 127 public function showEditDate($checkTimestamp = false) 128 { 129 $formatted = dformat($this->val('date')); 130 if ($checkTimestamp && $this->val('timestamp') === false) { 131 // exact date is unknown for externally deleted file 132 // when unknown, alter formatted string "YYYY-mm-DD HH:MM" to "____-__-__ __:__" 133 $formatted = preg_replace('/[0-9a-zA-Z]/', '_', $formatted); 134 } 135 return '<span class="date">' . $formatted . '</span>'; 136 } 137 138 /** 139 * edit summary 140 * used in [Ui\recent, Ui\Revisions] 141 * 142 * @return string 143 */ 144 public function showEditSummary() 145 { 146 return '<span class="sum">' . ' – ' . hsc($this->val('sum')) . '</span>'; 147 } 148 149 /** 150 * editor of the page or media file 151 * used in [Ui\recent, Ui\Revisions] 152 * 153 * @return string 154 */ 155 public function showEditor() 156 { 157 if ($this->val('user')) { 158 $html = '<bdi>' . editorinfo($this->val('user')) . '</bdi>'; 159 if (auth_ismanager()) $html .= ' <bdo dir="ltr">(' . $this->val('ip') . ')</bdo>'; 160 } else { 161 $html = '<bdo dir="ltr">' . $this->val('ip') . '</bdo>'; 162 } 163 return '<span class="user">' . $html . '</span>'; 164 } 165 166 /** 167 * name of the page or media file 168 * used in [Ui\recent, Ui\Revisions] 169 * 170 * @return string 171 */ 172 public function showFileName() 173 { 174 $id = $this->val('id'); 175 $rev = $this->isCurrent() ? '' : $this->val('date'); 176 177 if ($this->val('mode') == self::MODE_MEDIA) { 178 // media file revision 179 $params = ['tab_details' => 'view', 'ns' => getNS($id), 'image' => $id]; 180 if ($rev) $params += ['rev' => $rev]; 181 $href = media_managerURL($params, '&'); 182 $display_name = $id; 183 $exists = file_exists(mediaFN($id, $rev)); 184 } elseif ($this->val('mode') == self::MODE_PAGE) { 185 // page revision 186 $params = $rev ? ['rev' => $rev] : []; 187 $href = wl($id, $params, false, '&'); 188 $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id; 189 if (!$display_name) $display_name = $id; 190 $exists = page_exists($id, $rev); 191 } 192 193 if ($exists) { 194 $class = 'wikilink1'; 195 } elseif ($this->isCurrent()) { 196 //show only not-existing link for current page, which allows for directly create a new page/upload 197 $class = 'wikilink2'; 198 } else { 199 //revision is not in attic 200 return $display_name; 201 } 202 if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) { 203 $class = 'wikilink2'; 204 } 205 return '<a href="' . $href . '" class="' . $class . '">' . $display_name . '</a>'; 206 } 207 208 /** 209 * Revision Title for PageDiff table headline 210 * 211 * @return string 212 */ 213 public function showRevisionTitle() 214 { 215 global $lang; 216 217 if (!$this->val('date')) return '—'; 218 219 $id = $this->val('id'); 220 $rev = $this->isCurrent() ? '' : $this->val('date'); 221 $params = ($rev) ? ['rev' => $rev] : []; 222 223 // revision info may have timestamp key when external edits occurred 224 $date = ($this->val('timestamp') === false) 225 ? $lang['unknowndate'] 226 : dformat($this->val('date')); 227 228 229 if ($this->val('mode') == self::MODE_MEDIA) { 230 // media file revision 231 $href = ml($id, $params, false, '&'); 232 $exists = file_exists(mediaFN($id, $rev)); 233 } elseif ($this->val('mode') == self::MODE_PAGE) { 234 // page revision 235 $href = wl($id, $params, false, '&'); 236 $exists = page_exists($id, $rev); 237 } 238 if ($exists) { 239 $class = 'wikilink1'; 240 } elseif ($this->isCurrent()) { 241 //show only not-existing link for current page, which allows for directly create a new page/upload 242 $class = 'wikilink2'; 243 } else { 244 //revision is not in attic 245 return $id . ' [' . $date . ']'; 246 } 247 if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) { 248 $class = 'wikilink2'; 249 } 250 return '<bdi><a class="' . $class . '" href="' . $href . '">' . $id . ' [' . $date . ']' . '</a></bdi>'; 251 } 252 253 /** 254 * diff link icon in recent changes list, to compare (this) current revision with previous one 255 * all items in "recent changes" are current revision of the page or media 256 * 257 * @return string 258 */ 259 public function showIconCompareWithPrevious() 260 { 261 global $lang; 262 $id = $this->val('id'); 263 264 $href = ''; 265 if ($this->val('mode') == self::MODE_MEDIA) { 266 // media file revision 267 // unlike page, media file does not copied to media_attic when uploaded. 268 // diff icon will not be shown when external edit occurred 269 // because no attic file to be compared with current. 270 $revs = (new MediaChangeLog($id))->getRevisions(0, 1); 271 $showLink = (count($revs) && file_exists(mediaFN($id, $revs[0])) && file_exists(mediaFN($id))); 272 if ($showLink) { 273 $param = ['tab_details' => 'history', 'mediado' => 'diff', 'ns' => getNS($id), 'image' => $id]; 274 $href = media_managerURL($param, '&'); 275 } 276 } elseif ($this->val('mode') == self::MODE_PAGE) { 277 // page revision 278 // when a page just created anyway, it is natural to expect no older revisions 279 // even if it had once existed but deleted before. Simply ignore to check changelog. 280 if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) { 281 $href = wl($id, ['do' => 'diff'], false, '&'); 282 } 283 } 284 285 if ($href) { 286 return '<a href="' . $href . '" class="diff_link">' 287 . '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"' 288 . ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />' 289 . '</a>'; 290 } else { 291 return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />'; 292 } 293 } 294 295 /** 296 * diff link icon in revisions list, compare this revision with current one 297 * the icon does not displayed for the current revision 298 * 299 * @return string 300 */ 301 public function showIconCompareWithCurrent() 302 { 303 global $lang; 304 $id = $this->val('id'); 305 $rev = $this->isCurrent() ? '' : $this->val('date'); 306 307 $href = ''; 308 if ($this->val('mode') == self::MODE_MEDIA) { 309 // media file revision 310 if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) { 311 $param = ['mediado' => 'diff', 'image' => $id, 'rev' => $rev]; 312 $href = media_managerURL($param, '&'); 313 } 314 } elseif ($this->val('mode') == self::MODE_PAGE) { 315 // page revision 316 if (!$this->isCurrent()) { 317 $href = wl($id, ['rev' => $rev, 'do' => 'diff'], false, '&'); 318 } 319 } 320 321 if ($href) { 322 return '<a href="' . $href . '" class="diff_link">' 323 . '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"' 324 . ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />' 325 . '</a>'; 326 } else { 327 return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />'; 328 } 329 } 330 331 /** 332 * icon for revision action 333 * used in [Ui\recent] 334 * 335 * @return string 336 */ 337 public function showIconRevisions() 338 { 339 global $lang; 340 341 if (!actionOK('revisions')) { 342 return ''; 343 } 344 345 $id = $this->val('id'); 346 if ($this->val('mode') == self::MODE_MEDIA) { 347 // media file revision 348 $param = ['tab_details' => 'history', 'ns' => getNS($id), 'image' => $id]; 349 $href = media_managerURL($param, '&'); 350 } elseif ($this->val('mode') == self::MODE_PAGE) { 351 // page revision 352 $href = wl($id, ['do' => 'revisions'], false, '&'); 353 } 354 return '<a href="' . $href . '" class="revisions_link">' 355 . '<img src="' . DOKU_BASE . 'lib/images/history.png" width="12" height="14"' 356 . ' title="' . $lang['btn_revs'] . '" alt="' . $lang['btn_revs'] . '" />' 357 . '</a>'; 358 } 359 360 /** 361 * size change 362 * used in [Ui\recent, Ui\Revisions] 363 * 364 * @return string 365 */ 366 public function showSizeChange() 367 { 368 $class = 'sizechange'; 369 $value = filesize_h(abs($this->val('sizechange'))); 370 if ($this->val('sizechange') > 0) { 371 $class .= ' positive'; 372 $value = '+' . $value; 373 } elseif ($this->val('sizechange') < 0) { 374 $class .= ' negative'; 375 $value = '-' . $value; 376 } else { 377 $value = '±' . $value; 378 } 379 return '<span class="' . $class . '">' . $value . '</span>'; 380 } 381 382 /** 383 * current indicator, used in revision list 384 * not used in Ui\Recent because recent files are always current one 385 * 386 * @return string 387 */ 388 public function showCurrentIndicator() 389 { 390 global $lang; 391 return $this->isCurrent() ? '(' . $lang['current'] . ')' : ''; 392 } 393} 394