PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/mod/wiki/parser/markups/wikimarkup.php

https://github.com/kpike/moodle
PHP | 433 lines | 295 code | 80 blank | 58 comment | 60 complexity | cfa95dcd135bd1d01363c3a6a37471ed MD5 | raw file
  1. <?php
  2. /**
  3. * Generic & abstract parser functions & skeleton. It has some functions & generic stuff.
  4. *
  5. * @author Josep ArĂºs
  6. *
  7. * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
  8. * @package wiki
  9. */
  10. abstract class wiki_markup_parser extends generic_parser {
  11. protected $pretty_print = false;
  12. protected $printable = false;
  13. //page id
  14. protected $wiki_page_id;
  15. //sections
  16. protected $section_edit_text = "[edit]";
  17. protected $repeated_sections;
  18. protected $section_editing = true;
  19. //header & ToC
  20. protected $toc = array();
  21. /**
  22. * function wiki_parser_link_callback($link = "")
  23. *
  24. * Returns array('content' => "Inside the link", 'url' => "http://url.com/Wiki/Entry", 'new' => false).
  25. */
  26. private $linkgeneratorcallback = array('parser_utils', 'wiki_parser_link_callback');
  27. private $linkgeneratorcallbackargs = array();
  28. /**
  29. * Table generator callback
  30. */
  31. private $tablegeneratorcallback = array('parser_utils', 'wiki_parser_table_callback');
  32. /**
  33. * Get real path from relative path
  34. */
  35. private $realpathcallback = array('parser_utils', 'wiki_parser_real_path');
  36. private $realpathcallbackargs = array();
  37. /**
  38. * Before and after parsing...
  39. */
  40. protected function before_parsing() {
  41. $this->toc = array();
  42. $this->string = preg_replace('/\r\n/', "\n", $this->string);
  43. $this->string = preg_replace('/\r/', "\n", $this->string);
  44. $this->string .= "\n\n";
  45. if (!$this->printable && $this->section_editing) {
  46. $this->returnvalues['unparsed_text'] = $this->string;
  47. $this->string = $this->get_repeated_sections($this->string);
  48. }
  49. }
  50. protected function after_parsing() {
  51. if (!$this->printable) {
  52. $this->returnvalues['repeated_sections'] = array_unique($this->returnvalues['repeated_sections']);
  53. }
  54. $this->process_toc();
  55. $this->string = preg_replace("/\n\s/", "\n", $this->string);
  56. $this->string = preg_replace("/\n{2,}/", "\n", $this->string);
  57. $this->string = trim($this->string);
  58. $this->string .= "\n";
  59. }
  60. /**
  61. * Set options
  62. */
  63. protected function set_options($options) {
  64. parent::set_options($options);
  65. $this->returnvalues['link_count'] = array();
  66. $this->returnvalues['repeated_sections'] = array();
  67. $this->returnvalues['toc'] = "";
  68. foreach ($options as $name => $o) {
  69. switch ($name) {
  70. case 'link_callback':
  71. $callback = explode(':', $o);
  72. global $CFG;
  73. require_once($CFG->dirroot . $callback[0]);
  74. if (function_exists($callback[1])) {
  75. $this->linkgeneratorcallback = $callback[1];
  76. }
  77. break;
  78. case 'link_callback_args':
  79. if (is_array($o)) {
  80. $this->linkgeneratorcallbackargs = $o;
  81. }
  82. break;
  83. case 'real_path_callback':
  84. $callback = explode(':', $o);
  85. global $CFG;
  86. require_once($CFG->dirroot . $callback[0]);
  87. if (function_exists($callback[1])) {
  88. $this->realpathcallback = $callback[1];
  89. }
  90. break;
  91. case 'real_path_callback_args':
  92. if (is_array($o)) {
  93. $this->realpathcallbackargs = $o;
  94. }
  95. break;
  96. case 'table_callback':
  97. $callback = explode(':', $o);
  98. global $CFG;
  99. require_once($CFG->dirroot . $callback[0]);
  100. if (function_exists($callback[1])) {
  101. $this->tablegeneratorcallback = $callback[1];
  102. }
  103. break;
  104. case 'pretty_print':
  105. if ($o) {
  106. $this->pretty_print = true;
  107. }
  108. break;
  109. case 'pageid':
  110. $this->wiki_page_id = $o;
  111. break;
  112. case 'printable':
  113. if ($o) {
  114. $this->printable = true;
  115. }
  116. break;
  117. }
  118. }
  119. }
  120. /**
  121. * Generic block rules
  122. */
  123. protected function line_break_block_rule($match) {
  124. return '<hr />';
  125. }
  126. protected function list_block_rule($match) {
  127. preg_match_all("/^\ *([\*\#]{1,5})\ *((?:[^\n]|\n(?!(?:\ *[\*\#])|\n))+)/im", $match[1], $listitems, PREG_SET_ORDER);
  128. return $this->process_block_list($listitems) . $match[2];
  129. }
  130. protected function nowiki_block_rule($match) {
  131. return parser_utils::h('pre', $this->protect($match[1]));
  132. }
  133. /**
  134. * Generic tag rules
  135. */
  136. protected function nowiki_tag_rule($match) {
  137. return parser_utils::h('tt', $this->protect($match[1]));
  138. }
  139. /**
  140. * Header generation
  141. */
  142. protected function generate_header($text, $level) {
  143. $text = trim($text);
  144. if (!$this->pretty_print && $level == 1) {
  145. $text .= parser_utils::h('a', $this->section_edit_text, array('href' => "edit.php?pageid={$this->wiki_page_id}&section=" . urlencode($text), 'class' => 'wiki_edit_section'));
  146. }
  147. if ($level < 4) {
  148. $this->toc[] = array($level, $text);
  149. $num = count($this->toc);
  150. $text = parser_utils::h('a', "", array('name' => "toc-$num")) . $text;
  151. }
  152. return parser_utils::h('h' . $level, $text) . "\n\n";
  153. }
  154. /**
  155. * Table of contents processing after parsing
  156. */
  157. protected function process_toc() {
  158. if (empty($this->toc)) {
  159. return;
  160. }
  161. $toc = "";
  162. $currentsection = array(0, 0, 0);
  163. $i = 1;
  164. foreach ($this->toc as & $header) {
  165. switch ($header[0]) {
  166. case 1:
  167. $currentsection = array($currentsection[0] + 1, 0, 0);
  168. break;
  169. case 2:
  170. $currentsection[1]++;
  171. $currentsection[2] = 0;
  172. if ($currentsection[0] == 0) {
  173. $currentsection[0]++;
  174. }
  175. break;
  176. case 3:
  177. $currentsection[2]++;
  178. if ($currentsection[1] == 0) {
  179. $currentsection[1]++;
  180. }
  181. if ($currentsection[0] == 0) {
  182. $currentsection[0]++;
  183. }
  184. break;
  185. default:
  186. continue;
  187. }
  188. $number = "$currentsection[0]";
  189. if (!empty($currentsection[1])) {
  190. $number .= ".$currentsection[1]";
  191. if (!empty($currentsection[2])) {
  192. $number .= ".$currentsection[2]";
  193. }
  194. }
  195. $toc .= parser_utils::h('p', $number . ". " . parser_utils::h('a', $header[1], array('href' => "#toc-$i")), array('class' => 'wiki-toc-section-' . $header[0] . " wiki-toc-section"));
  196. $i++;
  197. }
  198. $this->returnvalues['toc'] = "<div class=\"wiki-toc\"><p class=\"wiki-toc-title\">Table of contents</p>$toc</div>";
  199. }
  200. /**
  201. * List helpers
  202. */
  203. private function process_block_list($listitems) {
  204. $list = array();
  205. foreach ($listitems as $li) {
  206. $text = str_replace("\n", "", $li[2]);
  207. $this->rules($text);
  208. if ($li[1][0] == '*') {
  209. $type = 'ul';
  210. } else {
  211. $type = 'ol';
  212. }
  213. $list[] = array(strlen($li[1]), $text, $type);
  214. }
  215. $type = $list[0][2];
  216. return "<$type>" . "\n" . $this->generate_list($list) . "\n" . "</$type>" . "\n";
  217. }
  218. /**
  219. * List generation function from an array of array(level, text)
  220. */
  221. protected function generate_list($listitems) {
  222. $list = "";
  223. $current_depth = 1;
  224. $next_depth = 1;
  225. $liststack = array();
  226. for ($lc = 0; $lc < count($listitems) && $next_depth; $lc++) {
  227. $cli = $listitems[$lc];
  228. $nli = isset($listitems[$lc + 1]) ? $listitems[$lc + 1] : null;
  229. $text = $cli[1];
  230. $current_depth = $next_depth;
  231. $next_depth = $nli ? $nli[0] : null;
  232. if ($next_depth == $current_depth || $next_depth == null) {
  233. $list .= parser_utils::h('li', $text) . "\n";
  234. } else if ($next_depth > $current_depth) {
  235. $next_depth = $current_depth + 1;
  236. $list .= "<li>" . $text . "\n";
  237. $list .= "<" . $nli[2] . ">" . "\n";
  238. $liststack[] = $nli[2];
  239. } else {
  240. $list .= parser_utils::h('li', $text) . "\n";
  241. for ($lv = $next_depth; $lv < $current_depth; $lv++) {
  242. $type = array_pop($liststack);
  243. $list .= "</$type>" . "\n" . "</li>" . "\n";
  244. }
  245. }
  246. }
  247. for ($lv = 1; $lv < $current_depth; $lv++) {
  248. $type = array_pop($liststack);
  249. $list .= "</$type>" . "\n" . "</li>" . "\n";
  250. }
  251. return $list;
  252. }
  253. /**
  254. * Table generation functions
  255. */
  256. protected function generate_table($table) {
  257. $table_html = call_user_func_array($this->tablegeneratorcallback, array($table));
  258. return $table_html;
  259. }
  260. protected function format_image($src, $alt, $caption = "", $align = 'left') {
  261. $src = $this->real_path($src);
  262. return parser_utils::h('div', parser_utils::h('p', $caption) . '<img src="' . $src . '" alt="' . $alt . '" />', array('class' => 'wiki_image_' . $align));
  263. }
  264. protected function real_path($url) {
  265. $callbackargs = array_merge(array($url), $this->realpathcallbackargs);
  266. return call_user_func_array($this->realpathcallback, $callbackargs);
  267. }
  268. /**
  269. * Link internal callback
  270. */
  271. protected function link($link, $anchor = "") {
  272. $link = trim($link);
  273. if (preg_match("/^(https?|s?ftp):\/\/.+$/i", $link)) {
  274. $link = trim($link, ",.?!");
  275. return array('content' => $link, 'url' => $link);
  276. } else {
  277. $callbackargs = $this->linkgeneratorcallbackargs;
  278. $callbackargs['anchor'] = $anchor;
  279. $link = call_user_func_array($this->linkgeneratorcallback, array($link, $callbackargs));
  280. if (isset($link['link_info'])) {
  281. $l = $link['link_info']['link'];
  282. unset($link['link_info']['link']);
  283. $this->returnvalues['link_count'][$l] = $link['link_info'];
  284. }
  285. return $link;
  286. }
  287. }
  288. /**
  289. * Format links
  290. */
  291. protected function format_link($text) {
  292. $matches = array();
  293. if (preg_match("/^([^\|]+)\|(.+)$/i", $text, $matches)) {
  294. $link = $matches[1];
  295. $content = trim($matches[2]);
  296. if (preg_match("/(.+)#(.*)/is", $link, $matches)) {
  297. $link = $this->link($matches[1], $matches[2]);
  298. } else if ($link[0] == '#') {
  299. $link = array('url' => "#" . urlencode(substr($link, 1)));
  300. } else {
  301. $link = $this->link($link);
  302. }
  303. $link['content'] = $content;
  304. } else {
  305. $link = $this->link($text);
  306. }
  307. if (isset($link['new']) && $link['new']) {
  308. $options = array('class' => 'wiki_newentry');
  309. } else {
  310. $options = array();
  311. }
  312. $link['content'] = $this->protect($link['content']);
  313. $link['url'] = $this->protect($link['url']);
  314. $options['href'] = $link['url'];
  315. if ($this->printable) {
  316. $options['href'] = '#'; //no target for the link
  317. }
  318. return array($link['content'], $options);
  319. }
  320. /**
  321. * Section editing
  322. */
  323. public function get_section($header, $text, $clean = false) {
  324. if ($clean) {
  325. $text = preg_replace('/\r\n/', "\n", $text);
  326. $text = preg_replace('/\r/', "\n", $text);
  327. $text .= "\n\n";
  328. }
  329. preg_match("/(.*?)(=\ *\Q$header\E\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is", $text, $match);
  330. if (!empty($match)) {
  331. return array($match[1], $match[2], $match[3]);
  332. } else {
  333. return false;
  334. }
  335. }
  336. protected function get_repeated_sections(&$text, $repeated = array()) {
  337. $this->repeated_sections = $repeated;
  338. return preg_replace_callback($this->blockrules['header']['expression'], array($this, 'get_repeated_sections_callback'), $text);
  339. }
  340. protected function get_repeated_sections_callback($match) {
  341. $num = strlen($match[1]);
  342. $text = trim($match[2]);
  343. if ($num == 1) {
  344. if (in_array($text, $this->repeated_sections)) {
  345. $this->returnvalues['repeated_sections'][] = $text;
  346. return $text . "\n";
  347. } else {
  348. $this->repeated_sections[] = $text;
  349. }
  350. }
  351. return $match[0];
  352. }
  353. }