You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

396 lines
13 KiB

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