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()) { 160 $html .= ' <bdo dir="ltr">(' . $this->val('ip') . ')</bdo>'; 161 } 162 } else { 163 $html = '<bdo dir="ltr">' . $this->val('ip') . '</bdo>'; 164 } 165 return '<span class="user">' . $html . '</span>'; 166 } 167 168 /** 169 * name of the page or media file 170 * used in [Ui\recent, Ui\Revisions] 171 * 172 * @return string 173 */ 174 public function showFileName() 175 { 176 $id = $this->val('id'); 177 $rev = $this->isCurrent() ? '' : $this->val('date'); 178 179 if ($this->val('mode') == self::MODE_MEDIA) { 180 // media file revision 181 $params = ['tab_details' => 'view', 'ns' => getNS($id), 'image' => $id]; 182 if ($rev) $params += ['rev' => $rev]; 183 $href = media_managerURL($params, '&'); 184 $display_name = $id; 185 $exists = file_exists(mediaFN($id, $rev)); 186 } elseif ($this->val('mode') == self::MODE_PAGE) { 187 // page revision 188 $params = $rev ? ['rev' => $rev] : []; 189 $href = wl($id, $params, false, '&'); 190 $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id; 191 if (!$display_name) $display_name = $id; 192 $exists = page_exists($id, $rev); 193 } 194 195 if ($exists) { 196 $class = 'wikilink1'; 197 } elseif ($this->isCurrent()) { 198 //show only not-existing link for current page, which allows for directly create a new page/upload 199 $class = 'wikilink2'; 200 } else { 201 //revision is not in attic 202 return $display_name; 203 } 204 if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) { 205 $class = 'wikilink2'; 206 } 207 return '<a href="' . $href . '" class="' . $class . '">' . $display_name . '</a>'; 208 } 209 210 /** 211 * Revision Title for PageDiff table headline 212 * 213 * @return string 214 */ 215 public function showRevisionTitle() 216 { 217 global $lang; 218 219 if (!$this->val('date')) return '—'; 220 221 $id = $this->val('id'); 222 $rev = $this->isCurrent() ? '' : $this->val('date'); 223 $params = ($rev) ? ['rev' => $rev] : []; 224 225 // revision info may have timestamp key when external edits occurred 226 $date = ($this->val('timestamp') === false) 227 ? $lang['unknowndate'] 228 : dformat($this->val('date')); 229 230 231 if ($this->val('mode') == self::MODE_MEDIA) { 232 // media file revision 233 $href = ml($id, $params, false, '&'); 234 $exists = file_exists(mediaFN($id, $rev)); 235 } elseif ($this->val('mode') == self::MODE_PAGE) { 236 // page revision 237 $href = wl($id, $params, false, '&'); 238 $exists = page_exists($id, $rev); 239 } 240 if ($exists) { 241 $class = 'wikilink1'; 242 } elseif ($this->isCurrent()) { 243 //show only not-existing link for current page, which allows for directly create a new page/upload 244 $class = 'wikilink2'; 245 } else { 246 //revision is not in attic 247 return $id . ' [' . $date . ']'; 248 } 249 if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) { 250 $class = 'wikilink2'; 251 } 252 return '<bdi><a class="' . $class . '" href="' . $href . '">' . $id . ' [' . $date . ']' . '</a></bdi>'; 253 } 254 255 /** 256 * diff link icon in recent changes list, to compare (this) current revision with previous one 257 * all items in "recent changes" are current revision of the page or media 258 * 259 * @return string 260 */ 261 public function showIconCompareWithPrevious() 262 { 263 global $lang; 264 $id = $this->val('id'); 265 266 $href = ''; 267 if ($this->val('mode') == self::MODE_MEDIA) { 268 // media file revision 269 // unlike page, media file does not copied to media_attic when uploaded. 270 // diff icon will not be shown when external edit occurred 271 // because no attic file to be compared with current. 272 $revs = (new MediaChangeLog($id))->getRevisions(0, 1); 273 $showLink = (count($revs) && file_exists(mediaFN($id, $revs[0])) && file_exists(mediaFN($id))); 274 if ($showLink) { 275 $param = ['tab_details' => 'history', 'mediado' => 'diff', 'ns' => getNS($id), 'image' => $id]; 276 $href = media_managerURL($param, '&'); 277 } 278 } elseif ($this->val('mode') == self::MODE_PAGE) { 279 // page revision 280 // when a page just created anyway, it is natural to expect no older revisions 281 // even if it had once existed but deleted before. Simply ignore to check changelog. 282 if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) { 283 $href = wl($id, ['do' => 'diff'], false, '&'); 284 } 285 } 286 287 if ($href) { 288 return '<a href="' . $href . '" class="diff_link">' 289 . '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"' 290 . ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />' 291 . '</a>'; 292 } else { 293 return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />'; 294 } 295 } 296 297 /** 298 * diff link icon in revisions list, compare this revision with current one 299 * the icon does not displayed for the current revision 300 * 301 * @return string 302 */ 303 public function showIconCompareWithCurrent() 304 { 305 global $lang; 306 $id = $this->val('id'); 307 $rev = $this->isCurrent() ? '' : $this->val('date'); 308 309 $href = ''; 310 if ($this->val('mode') == self::MODE_MEDIA) { 311 // media file revision 312 if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) { 313 $param = ['mediado' => 'diff', 'image' => $id, 'rev' => $rev]; 314 $href = media_managerURL($param, '&'); 315 } 316 } elseif ($this->val('mode') == self::MODE_PAGE) { 317 // page revision 318 if (!$this->isCurrent()) { 319 $href = wl($id, ['rev' => $rev, 'do' => 'diff'], false, '&'); 320 } 321 } 322 323 if ($href) { 324 return '<a href="' . $href . '" class="diff_link">' 325 . '<img src="' . DOKU_BASE . 'lib/images/diff.png" width="15" height="11"' 326 . ' title="' . $lang['diff'] . '" alt="' . $lang['diff'] . '" />' 327 . '</a>'; 328 } else { 329 return '<img src="' . DOKU_BASE . 'lib/images/blank.gif" width="15" height="11" alt="" />'; 330 } 331 } 332 333 /** 334 * icon for revision action 335 * used in [Ui\recent] 336 * 337 * @return string 338 */ 339 public function showIconRevisions() 340 { 341 global $lang; 342 343 if (!actionOK('revisions')) { 344 return ''; 345 } 346 347 $id = $this->val('id'); 348 if ($this->val('mode') == self::MODE_MEDIA) { 349 // media file revision 350 $param = ['tab_details' => 'history', 'ns' => getNS($id), 'image' => $id]; 351 $href = media_managerURL($param, '&'); 352 } elseif ($this->val('mode') == self::MODE_PAGE) { 353 // page revision 354 $href = wl($id, ['do' => 'revisions'], false, '&'); 355 } 356 return '<a href="' . $href . '" class="revisions_link">' 357 . '<img src="' . DOKU_BASE . 'lib/images/history.png" width="12" height="14"' 358 . ' title="' . $lang['btn_revs'] . '" alt="' . $lang['btn_revs'] . '" />' 359 . '</a>'; 360 } 361 362 /** 363 * size change 364 * used in [Ui\recent, Ui\Revisions] 365 * 366 * @return string 367 */ 368 public function showSizeChange() 369 { 370 $class = 'sizechange'; 371 $value = filesize_h(abs($this->val('sizechange'))); 372 if ($this->val('sizechange') > 0) { 373 $class .= ' positive'; 374 $value = '+' . $value; 375 } elseif ($this->val('sizechange') < 0) { 376 $class .= ' negative'; 377 $value = '-' . $value; 378 } else { 379 $value = '±' . $value; 380 } 381 return '<span class="' . $class . '">' . $value . '</span>'; 382 } 383 384 /** 385 * current indicator, used in revision list 386 * not used in Ui\Recent because recent files are always current one 387 * 388 * @return string 389 */ 390 public function showCurrentIndicator() 391 { 392 global $lang; 393 return $this->isCurrent() ? '(' . $lang['current'] . ')' : ''; 394 } 395} 396