PageRenderTime 41ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/inc/wiki.php

https://bitbucket.org/yoander/mtrack
PHP | 432 lines | 326 code | 56 blank | 50 comment | 53 complexity | d8a604b43f6def3a0ddb8e6fbc890a63 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 MTRACK_INC_DIR . '/wiki/trac.php';
  4. include MTRACK_INC_DIR . '/lib/markdown.php';
  5. class MTrackWiki {
  6. static $macros = array();
  7. static $processors = array();
  8. static $wikiContext = null;
  9. static function rest_render_html($method, $uri, $captures) {
  10. MTrackAPI::checkAllowed($method, 'PUT', 'POST');
  11. $text = MTrackAPI::getPayload(true);
  12. return array('html' => self::format_to_html($text,
  13. MTrackAPI::getParam('wikiContext')));
  14. }
  15. static function rest_render_oneline($method, $uri, $captures) {
  16. MTrackAPI::checkAllowed($method, 'PUT', 'POST');
  17. $text = MTrackAPI::getPayload(true);
  18. return array('html' => self::format_to_oneliner($text));
  19. }
  20. static function format_to_html($text, $wikiContext = null) {
  21. if (MTrackWikiItem::is_content_conflicted($text)) {
  22. return "<em>The text is conflicted; resolve the conflict by removing the chevrons and pipes that demark the &quot;mine&quot;, &quot;original&quot; and &quot;theirs&quot; sections</em><br><pre>" . htmlentities($text, ENT_QUOTES, 'UTF-8') . "</pre>";
  23. }
  24. $origPage = self::$wikiContext;
  25. self::$wikiContext = $wikiContext;
  26. if (MTrackConfig::get('core', 'wikisyntax') == 'markdown') {
  27. $html = mtrack_markdown($text);
  28. } else {
  29. $f = new MTrackWikiHTMLFormatter;
  30. $f->format($text);
  31. $html = $f->out;
  32. }
  33. self::$wikiContext = $origPage;
  34. return $html;
  35. }
  36. /* despite the name, we actually take up to 3 lines
  37. * as a summary */
  38. static function format_to_oneliner($text, $limit = 3) {
  39. $lines = explode("\n", $text);
  40. if (count($lines) > $limit + 1) {
  41. $lines = array_slice($lines, 0, $limit);
  42. $text = join("\n", $lines);
  43. if (MTrackConfig::get('core', 'wikisyntax') == 'markdown') {
  44. $text .= "\n<br> __*truncated*__";
  45. } else {
  46. $text .= "[[BR]] ''truncated''";
  47. }
  48. }
  49. return self::format_to_html($text);
  50. if (MTrackConfig::get('core', 'wikisyntax') == 'markdown') {
  51. $html = mtrack_markdown($text, true);
  52. } else {
  53. $f = new MTrackWikiOneLinerFormatter;
  54. $f->format($text);
  55. $html = $f->out;
  56. }
  57. return $html;
  58. }
  59. static function format_to_multiliner($text) {
  60. $f = new MTrackWikiMultiLinerFormatter;
  61. $f->format($text);
  62. return $f->out;
  63. }
  64. static function format_wiki_page($name) {
  65. $d = MTrackWikiItem::loadByPageName($name);
  66. if ($d) {
  67. return self::format_to_html($d->content);
  68. }
  69. return null;
  70. }
  71. static function has_macro($name) {
  72. return isset(self::$macros[$name]);
  73. }
  74. static function run_macro($name, $args) {
  75. if (is_string($args)) {
  76. $args = preg_split('/\s*,\s*/', $args);
  77. }
  78. if (!is_array($args)) {
  79. $args = array();
  80. }
  81. return call_user_func_array(self::$macros[$name], $args);
  82. }
  83. static function register_macro($name, $callback) {
  84. self::$macros[$name] = $callback;
  85. }
  86. static function register_processor($name, $callback) {
  87. self::$processors[$name] = $callback;
  88. }
  89. static function has_processor($name) {
  90. return isset(self::$processors[$name]);
  91. }
  92. static function process_codeblock($text) {
  93. if (!is_array($text)) {
  94. $text = explode("\n", $text);
  95. }
  96. if (preg_match("/^#!(\S+)$/", $text[0], $M) &&
  97. self::has_processor($M[1])) {
  98. array_shift($text);
  99. return self::run_processor($M[1], $text);
  100. }
  101. return "<pre>" .
  102. htmlspecialchars(join("\n", $text), ENT_COMPAT, 'utf-8') .
  103. "</pre>";
  104. }
  105. static function run_processor($name, $text_lines) {
  106. if (!is_array($text_lines)) {
  107. $text_lines = explode("\n", $text_lines);
  108. }
  109. if (!self::has_processor($name)) {
  110. return "<pre>" .
  111. htmlentities(
  112. join("\n", $text_lines),
  113. ENT_COMPAT, 'utf-8') .
  114. "</pre>";
  115. }
  116. return call_user_func(self::$processors[$name], $name, $text_lines);
  117. }
  118. static function _doc_comment_cleanup($comment) {
  119. $comment = preg_replace('!^/\*{2,}\s*!', '', $comment);
  120. $comment = preg_replace('!\s*\*+/$!', '', $comment);
  121. return $comment;
  122. }
  123. /** Summarizes available macros */
  124. static function macro_show_macros() {
  125. $md = <<<MARKDOWN
  126. | Macro | Description |
  127. | ----------------------- | ----------- |
  128. MARKDOWN;
  129. foreach (self::$macros as $name => $func) {
  130. if (is_array($func)) {
  131. list($class, $fname) = $func;
  132. } else if (preg_match("/^(.*)::(.*)$/", $func, $M)) {
  133. list(, $class, $fname) = $M;
  134. } else {
  135. $class = null;
  136. $fname = $func;
  137. }
  138. if ($class) {
  139. $R = new ReflectionMethod($class, $fname);
  140. } else {
  141. $R = new ReflectionFunction($fname);
  142. }
  143. $comment = self::_doc_comment_cleanup($R->getDocComment());
  144. if (!strlen($comment)) {
  145. $comment = "No docs for $class::$fname";
  146. }
  147. $md .= "| $name | $comment |\n";
  148. }
  149. return mtrack_markdown($md);
  150. }
  151. /** Summarizes available block processors */
  152. static function macro_show_processors() {
  153. $md = <<<MARKDOWN
  154. | Processor | Description |
  155. | --------- | ----------- |
  156. MARKDOWN;
  157. foreach (self::$processors as $name => $func) {
  158. if (is_array($func)) {
  159. list($class, $fname) = $func;
  160. } else if (preg_match("/^(.*)::(.*)$/", $func, $M)) {
  161. list(, $class, $fname) = $M;
  162. } else {
  163. $class = null;
  164. $fname = $func;
  165. }
  166. if ($class) {
  167. $R = new ReflectionMethod($class, $fname);
  168. } else {
  169. $R = new ReflectionFunction($fname);
  170. }
  171. $comment = self::_doc_comment_cleanup($R->getDocComment());
  172. if (!strlen($comment)) {
  173. $comment = "No docs for $class::$fname";
  174. }
  175. $md .= "| $name | $comment |\n";
  176. }
  177. return mtrack_markdown($md);
  178. }
  179. /** Includes content of named wiki page and renders it as HTML */
  180. static function macro_IncludeWiki($pagename) {
  181. return self::format_wiki_page($pagename);
  182. }
  183. /** Includes content of named help page and renders it as HTML */
  184. static function macro_IncludeHelp($pagename) {
  185. return mtrack_markdown(file_get_contents(
  186. dirname(__FILE__) . '/../defaults/help/' . basename($pagename) . '.md'));
  187. }
  188. /** Renders an inline image. See [help:WikiFormatting#Images] */
  189. static function macro_image($name) {
  190. /* Ugh! Hate these special cases, but we only allow two types
  191. * of attachment bearing objects right now.
  192. * This is crying out for an actual API for registering object
  193. * identifiers with the system and a way to query those for the URL.
  194. */
  195. if (preg_match(',^(.*)/(\d+)/([^/]+)$,', $name, $M)) {
  196. // Fully qualified object id
  197. // [[Image(ticket:4e8bb46ac7e046d7ab312dac00000000/2221/default.png)]]
  198. $object = $M[1];
  199. $name = $M[3];
  200. } elseif (preg_match(',^([a-z]+):([a-fA-F0-9]{32})/([^/]+)$,', $name, $M)){
  201. // [[Image(ticket:4e8bb46ac7e046d7ab312dac00000000/default.png)]]
  202. $object = $M[1] . ':' . $M[2];
  203. $name = $M[3];
  204. } elseif (preg_match(',^ticket:#?(\d+):([^/]+)$,', $name, $M)){
  205. // [[Image(ticket:#3:default.png)]]
  206. // [[Image(ticket:3:default.png)]]
  207. $T = MTrackIssue::loadByNSIdent($M[1]);
  208. $object = "ticket:$T->tid";
  209. $name = $M[2];
  210. } elseif (preg_match(',^(wiki:.+):([^/]+)$,', $name, $M)){
  211. // [[Image(wiki:WikiStartHello:photo.JPG)]]
  212. $object = $M[1];
  213. $name = $M[2];
  214. } else {
  215. $object = MTrackWiki::$wikiContext;
  216. }
  217. $args = func_get_args();
  218. $A = MTrackAttachment::getAttachment($object, $name);
  219. if (!$A) {
  220. // Invalid
  221. return "[[Image(" .
  222. htmlentities(join(",", $args), ENT_QUOTES, 'utf-8') . ")]]";
  223. }
  224. // shift away the name
  225. array_shift($args);
  226. $params = array();
  227. // Scale it down to a reasonable default
  228. if ($A->width > 500) {
  229. $params['width'] = 500;
  230. }
  231. $params['alt'] = $A->filename;
  232. $use_link = true;
  233. foreach ($args as $arg) {
  234. if (preg_match("/^(\d+)px$/", $arg, $M)) {
  235. $params['width'] = $M[1];
  236. continue;
  237. }
  238. if (preg_match("/^(.*)\s*=\s*(.*)$/", $arg, $M)) {
  239. $params[$M[1]] = $M[2];
  240. continue;
  241. }
  242. if ($arg == 'nolink') {
  243. $use_link = false;
  244. continue;
  245. }
  246. $params['align'] = $arg;
  247. }
  248. if (isset($params['width']) && !isset($params['height'])) {
  249. // Auto-scale
  250. $scale = $A->width / $params['width'];
  251. $params['height'] = ceil($A->height / $scale);
  252. }
  253. if (!isset($params['width']) && !isset($params['height'])) {
  254. $params['width'] = $A->width;
  255. $params['height'] = $A->height;
  256. }
  257. $img = "<img src='" . htmlentities($A->url, ENT_QUOTES, 'utf-8') . "'";
  258. foreach ($params as $k => $v) {
  259. $img .= " $k='" . htmlentities($v, ENT_QUOTES, 'utf-8') . "'";
  260. }
  261. $img .= ">";
  262. if ($use_link) {
  263. $img = "<a href='" . htmlentities($A->url, ENT_QUOTES, 'utf-8')
  264. . "' title='" .
  265. htmlentities($params['alt'], ENT_QUOTES, 'utf-8') . "'>$img</a>";
  266. }
  267. $params['object'] = $object;
  268. $params['name'] = $name;
  269. return $img;// . json_encode($params);
  270. }
  271. static function macro_comment() {
  272. return '';
  273. }
  274. /** Ignores text and emits an empty block instead; useful for commenting markup */
  275. static function processor_comment($name, $content) {
  276. return '';
  277. }
  278. /** Interprets text as raw HTML and passes it through unchanged */
  279. static function processor_html($name, $content) {
  280. return join("\n", $content);
  281. }
  282. /** Interpets text as tabular data output from SQL utilities and renders it as a table */
  283. static function processor_dataset($name, $content) {
  284. $res = '<table class="report wiki dataset">';
  285. while (count($content)) {
  286. $row = array_shift($content);
  287. $next_row = array_shift($content);
  288. $cols = preg_split("/\s*\|\s*/", $row);
  289. if ($next_row[0] == '-') {
  290. // it's a header
  291. $res .= '<thead><tr>';
  292. foreach ($cols as $c) {
  293. $res .= "<th>" . htmlentities($c, ENT_QUOTES, 'utf-8') . "</th>\n";
  294. }
  295. $res .= "</tr></thead><tbody>";
  296. } else {
  297. if (is_string($next_row)) {
  298. array_unshift($content, $next_row);
  299. }
  300. // regular row
  301. $res .= "<tr>";
  302. foreach ($cols as $c) {
  303. $res .= "<td>" . htmlentities($c, ENT_QUOTES, 'utf-8') . "</td>\n";
  304. }
  305. $res .= "</tr>\n";
  306. }
  307. }
  308. $res .= "</tbody></table>\n";
  309. return $res;
  310. }
  311. static function resolve_wiki_link(MTrackLink $link)
  312. {
  313. $link->class = 'wikilink';
  314. $link->url = $GLOBALS['ABSWEB'] . 'wiki.php/' . $link->target;
  315. }
  316. static function resolve_help_link(MTrackLink $link)
  317. {
  318. $link->class = 'wikilink';
  319. $link->url = $GLOBALS['ABSWEB'] . 'help.php/' . $link->target;
  320. }
  321. }
  322. function mtrack_markdown($text, $oneline = false) {
  323. $text = Markdown($text, array('nl2br' => !$oneline));
  324. if ($oneline) {
  325. // This is likely to need some improvement!
  326. $text = preg_replace('{</?[pP]>}', '', $text);
  327. }
  328. return $text;
  329. }
  330. /** Interprets text as Markdown and transforms it to HTML */
  331. function mtrack_markdown_processor($name, $content) {
  332. return mtrack_markdown(join("\n", $content));
  333. }
  334. MTrackWiki::register_processor('markdown', 'mtrack_markdown_processor');
  335. MTrackLink::register('wiki', 'MTrackWiki::resolve_wiki_link');
  336. MTrackLink::register('help', 'MTrackWiki::resolve_help_link');
  337. MTrackAPI::register('/wiki/render/html', 'MTrackWiki::rest_render_html');
  338. MTrackAPI::register('/wiki/render/oneline', 'MTrackWiki::rest_render_oneline');
  339. MTrackWiki::register_macro('ListRegisteredBlockProcessors',
  340. array('MTrackWiki', 'macro_show_processors'));
  341. MTrackWiki::register_macro('ListRegisteredMacros',
  342. array('MTrackWiki', 'macro_show_macros'));
  343. MTrackWiki::register_macro('IncludeWikiPage',
  344. array('MTrackWiki', 'macro_IncludeWiki'));
  345. MTrackWiki::register_macro('IncludeHelpPage',
  346. array('MTrackWiki', 'macro_IncludeHelp'));
  347. MTrackWiki::register_macro('Comment',
  348. array('MTrackWiki', 'macro_comment'));
  349. MTrackWiki::register_macro('Image',
  350. array('MTrackWiki', 'macro_image'));
  351. MTrackWiki::register_processor('comment',
  352. array('MTrackWiki', 'processor_comment'));
  353. MTrackWiki::register_processor('html',
  354. array('MTrackWiki', 'processor_html'));
  355. MTrackWiki::register_processor('dataset',
  356. array('MTrackWiki', 'processor_dataset'));
  357. /*
  358. #error_reporting(E_NOTICE);
  359. $f = new MTrackWikiHTMLFormatter;
  360. $f->format(file_get_contents("WikiFormatting"));
  361. #$f->format("* '''wooot'''\noh '''yeah'''\n\n");
  362. #$f->format(" < wez@php.net http://foo.com/bar [https://baz.com/flib Flib] [/foo Shoe]\n");
  363. /*
  364. $f->format(<<<WIKI
  365. >> foo
  366. > bar
  367. all done
  368. WIKI
  369. );
  370. */
  371. /*
  372. echo $f->out, "\n";
  373. print_r($f->missing);
  374. echo "\ndone\n";
  375. */