PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/web/help.php

https://bitbucket.org/yoander/mtrack
PHP | 304 lines | 259 code | 34 blank | 11 comment | 33 complexity | 17adadc359d5946941ea1747ce146915 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. include '../inc/common.php';
  4. $topic = mtrack_get_pathinfo();
  5. $helpdir = dirname(__FILE__) . '/../defaults/help';
  6. if (strpos($topic, '..') !== false) {
  7. throw new Exception("invalid help topic");
  8. }
  9. $name = $helpdir . '/' . $topic;
  10. function build_help_tree($tree, $dir) {
  11. foreach (scandir($dir) as $ent) {
  12. if ($ent[0] == '.') {
  13. continue;
  14. }
  15. $full = $dir . DIRECTORY_SEPARATOR . $ent;
  16. if (is_dir($full)) {
  17. $kid = new stdclass;
  18. build_help_tree($kid, $full);
  19. $tree->{$ent} = $kid;
  20. } else {
  21. $tree->$ent = $ent;
  22. }
  23. }
  24. }
  25. function walk_tree(&$col, $base, $tree, $type)
  26. {
  27. if ($base != '') {
  28. $base .= '/';
  29. }
  30. foreach ($tree as $name => $val) {
  31. if (is_object($val)) {
  32. walk_tree($col, $base . $name, $val, $type);
  33. } else {
  34. $o = new stdclass;
  35. $o->type = ucfirst($type);
  36. $o->name = $name;
  37. $o->fullname = $base . $name;
  38. if ($type == 'help') {
  39. $o->fullname = preg_replace('/\.md$/', '', $o->fullname);
  40. }
  41. $o->lower = strtolower($o->fullname);
  42. $o->url = $type . '.php/' . $o->fullname;
  43. $col[] = $o;
  44. }
  45. }
  46. }
  47. function get_collection() {
  48. $col = array();
  49. $htree = mtrack_cache('get_help_tree', array());
  50. walk_tree($col, '', $htree, 'help');
  51. if (MTrackACL::hasAnyRights('Wiki', 'read')) {
  52. $tree = MTrackWikiItem::get_wiki_tree();
  53. walk_tree($col, '', $tree, 'wiki');
  54. }
  55. return $col;
  56. }
  57. function get_help_tree() {
  58. $htree = new stdclass;
  59. build_help_tree($htree, dirname(__FILE__) . '/../defaults/help');
  60. return $htree;
  61. }
  62. if (!strlen($topic)) {
  63. mtrack_head("Help topics");
  64. $pages = json_encode(get_collection());
  65. if (MTrackACL::hasAnyRights('Wiki', 'read')) {
  66. $recent = MTrackWikiItem::get_recent_changes();
  67. } else {
  68. $recent = array();
  69. }
  70. $recent = json_encode($recent);
  71. echo <<<HTML
  72. <style>
  73. #hsearch {
  74. width: 20em;
  75. font-size: 2em;
  76. }
  77. #results {
  78. margin-top: 1em;
  79. font-size: 1.2em;
  80. }
  81. #results li {
  82. line-height: 1.5em;
  83. max-width: 40em;
  84. }
  85. #recentwiki {
  86. position: fixed;
  87. right: 1em;
  88. width: 20em;
  89. top: 7em;
  90. bottom: 0px;
  91. overflow-y: hidden;
  92. }
  93. </style>
  94. <h1>Help &amp; Wiki</h1>
  95. <label>Search Wiki and Help topics</label><br>
  96. <input type="text" id="hsearch" placeholder="Type here to search">
  97. <ul id="results"></ul>
  98. <div id="recentwiki">
  99. <h2>Recent Wiki Changes</h2>
  100. <div id="changelist"></div>
  101. </div>
  102. <script type="text/template" id='change-template'>
  103. <div class='ticketevent'>
  104. <abbr class='timeinterval' title='<%- when %>'><%- when %></abbr></a> <%- who %>
  105. </div>
  106. <div class='ticketchangeinfo'>
  107. <img class='gravatar' src="${ABSWEB}avatar.php?u=<%- who %>&amp;s=48">
  108. <% _.each(pages, function(page) { %>
  109. <a class='wikilink' href='<%= ABSWEB %>wiki.php/<%- page %>?rev=<%- rev %>'><%- page %></a>
  110. <% }); %>
  111. <br/>
  112. <div class='wiki-change-desc'>
  113. <%= changelog_html %>
  114. </div>
  115. </div>
  116. </script>
  117. <script type='text/javascript'>
  118. $(document).ready(function(){
  119. var recent = $recent;
  120. var t = _.template($('#change-template').html());
  121. var rlist = $('#changelist');
  122. _.each(recent, function (ent) {
  123. rlist.append(t(ent));
  124. });
  125. $('abbr', rlist).timeago();
  126. var pages = $pages;
  127. var wiki_by_name = {};
  128. _.each(pages, function (p) {
  129. if (p.type == 'Wiki') {
  130. wiki_by_name[p.fullname] = p;
  131. }
  132. });
  133. var S = $('#hsearch');
  134. var lastval = window.location.hash.substr(1);
  135. S.val(lastval);
  136. var timer = null;
  137. var ajax = null;
  138. S.attr('autocomplete', 'off');
  139. function make_entry(ul, page) {
  140. var li = $('<li/>');
  141. li.attr('mtrack-fullname', page.fullname);
  142. li.append('<span>' + page.type + ':</span> ');
  143. var a = $('<a/>');
  144. a.text(page.fullname);
  145. a.attr('href', ABSWEB + page.url);
  146. if (page.type == 'Wiki') {
  147. a.addClass('wikilink');
  148. } else {
  149. a.addClass('helplink');
  150. }
  151. li.append(a);
  152. ul.append(li);
  153. return li;
  154. }
  155. function show_list(res) {
  156. var ul = $('#results');
  157. ul.empty();
  158. _.each(res, function (page) {
  159. make_entry(ul, page);
  160. });
  161. }
  162. function annotate_list(res) {
  163. var ul = $('#results');
  164. _.each(res.results, function (r) {
  165. var page = wiki_by_name[r.id];
  166. if (!page) {
  167. /* it's been deleted but is still in the index */
  168. return;
  169. }
  170. /* See if we already have an entry for this guy in the list */
  171. var li = null;
  172. $('li', ul).each(function () {
  173. if ($(this).attr('mtrack-fullname') == r.id) {
  174. li = $(this);
  175. }
  176. });
  177. /* if not, then we need to create one */
  178. if (!li) {
  179. li = make_entry(ul, page);
  180. }
  181. _.each(r.hits, function (hit) {
  182. $(hit.excerpt).appendTo(li);
  183. });
  184. });
  185. }
  186. function search(q) {
  187. // Generate list of matching items
  188. // Compute the score for this item
  189. var biggest = 0;
  190. var res = [];
  191. if (q.length <= 2) {
  192. show_list(pages);
  193. return;
  194. }
  195. _.each(pages, function (page) {
  196. if ('cache' in page) {
  197. if (q in page.cache) {
  198. page.s = page.cache[q];
  199. res.push(page);
  200. return;
  201. }
  202. } else {
  203. page.cache = {};
  204. }
  205. // compute
  206. page.s = page.lower.QuickSilverScore(q);
  207. page.cache[q] = page.s;
  208. if (page.s > 0) {
  209. res.push(page);
  210. }
  211. });
  212. res.sort(function (a, b) { return b.s - a.s; });
  213. show_list(res);
  214. ajax = $.ajax({
  215. url: ABSWEB + "api.php/search/query",
  216. dataType: 'json',
  217. data: {
  218. q: "+type:wiki +\"" + q + "\""
  219. },
  220. success: function (data) {
  221. annotate_list(data);
  222. },
  223. complete: function (xhr, text) {
  224. ajax = null;
  225. }
  226. });
  227. }
  228. search(S.val().toLowerCase());
  229. S.bind('keyup.mtrack', function () {
  230. var q = $(this).val();
  231. if (q == lastval) {
  232. return;
  233. }
  234. lastval = q;
  235. q = q.toLowerCase();
  236. if (ajax) {
  237. ajax.abort();
  238. ajax = null;
  239. }
  240. clearTimeout(timer);
  241. timer = setTimeout(function () {
  242. search(q.toLowerCase());
  243. }, 250);
  244. });
  245. });
  246. </script>
  247. HTML;
  248. } elseif (!file_exists("$name.md")) {
  249. header("HTTP/1.0 404 Not Found");
  250. mtrack_head("no help topic $topic");
  251. echo "<h1>No Help topic ", htmlentities($topic), "</h1>";
  252. } else {
  253. mtrack_head("Help: $topic");
  254. $wiki = mtrack_markdown(file_get_contents("$name.md"));
  255. echo <<<HTML
  256. <div id='wikiview'>
  257. <div id='wiki' class='wikipage'>$wiki</div>
  258. </div>
  259. <div id='wikiinfo'>
  260. <label>Page Outline</label>
  261. <ol id='outline'></ul>
  262. </div>
  263. <script>
  264. $(document).ready(function () {
  265. mtrack_wiki_outline($('#wiki'), $('#outline'));
  266. });
  267. </script>
  268. HTML;
  269. }
  270. mtrack_foot();