PageRenderTime 37ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/qna/action.php

http://dwp-forge.googlecode.com/
PHP | 374 lines | 236 code | 59 blank | 79 comment | 44 complexity | 3fde3245e566cb6b3c0a33445b05aa36 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * Plugin QnA: Layout parser
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Mykola Ostrovskyy <spambox03@mail.ru>
  7. */
  8. /* Must be run within Dokuwiki */
  9. if(!defined('DOKU_INC')) die();
  10. if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
  11. require_once(DOKU_PLUGIN . 'action.php');
  12. require_once(DOKU_PLUGIN . 'qna/info.php');
  13. class action_plugin_qna extends DokuWiki_Action_Plugin {
  14. const STATE_CLOSED = 0;
  15. const STATE_QUESTION = 1;
  16. const STATE_ANSWER = 2;
  17. private $rewriter;
  18. private $blockState;
  19. private $headerIndex;
  20. private $headerTitle;
  21. private $headerLevel;
  22. private $headerId;
  23. /**
  24. * Return some info
  25. */
  26. public function getInfo() {
  27. return qna_getInfo('layout parser');
  28. }
  29. /**
  30. * Register callbacks
  31. */
  32. public function register($controller) {
  33. $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'afterParserHandlerDone');
  34. $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'beforeParserCacheUse');
  35. }
  36. /**
  37. *
  38. */
  39. public function afterParserHandlerDone($event, $param) {
  40. $this->reset();
  41. $this->fixLayout($event);
  42. }
  43. /**
  44. * Reset internal state
  45. */
  46. private function reset() {
  47. $this->rewriter = new qna_instruction_rewriter();
  48. $this->blockState = self::STATE_CLOSED;
  49. $this->headerIndex = -1;
  50. $this->headerTitle = '';
  51. $this->headerLevel = 0;
  52. $this->headerId = array();
  53. }
  54. /**
  55. * Insert implicit instructions
  56. */
  57. private function fixLayout($event) {
  58. $instructions = count($event->data->calls);
  59. for ($i = 0; $i < $instructions; $i++) {
  60. $instruction = $event->data->calls[$i];
  61. switch ($instruction[0]) {
  62. case 'header':
  63. $this->headerIndex = $i;
  64. $this->headerTitle = $instruction[1][0];
  65. $this->headerLevel = $instruction[1][1];
  66. sectionID($instruction[1][0], $this->headerId);
  67. /* Fall through */
  68. case 'section_close':
  69. case 'section_edit':
  70. case 'section_open':
  71. if ($this->blockState != self::STATE_CLOSED) {
  72. $this->rewriter->insertBlockCall($i, 'close_block', 2);
  73. $this->blockState = self::STATE_CLOSED;
  74. }
  75. break;
  76. case 'plugin':
  77. switch ($instruction[1][0]) {
  78. case 'qna_block':
  79. $this->handlePluginQnaBlock($i, $instruction[1][1]);
  80. break;
  81. case 'qna_header':
  82. $this->handlePluginQnaHeader($i);
  83. break;
  84. }
  85. break;
  86. }
  87. }
  88. if ($this->blockState != self::STATE_CLOSED) {
  89. $this->rewriter->appendBlockCall('close_block', 2);
  90. }
  91. $this->rewriter->apply($event->data->calls);
  92. }
  93. /**
  94. * Insert implicit instructions
  95. */
  96. private function handlePluginQnaBlock($index, $data) {
  97. switch ($data[0]) {
  98. case 'open_question':
  99. if ($this->blockState != self::STATE_CLOSED) {
  100. $this->rewriter->insertBlockCall($index, 'close_block', 2);
  101. }
  102. $this->rewriter->insertBlockCall($index, 'open_block');
  103. $this->rewriter->setQuestionLevel($index, $this->headerLevel + 1);
  104. $this->blockState = self::STATE_QUESTION;
  105. break;
  106. case 'open_answer':
  107. switch ($this->blockState) {
  108. case self::STATE_CLOSED:
  109. $this->rewriter->delete($index);
  110. break;
  111. case self::STATE_QUESTION:
  112. case self::STATE_ANSWER:
  113. $this->rewriter->insertBlockCall($index, 'close_block');
  114. $this->blockState = self::STATE_ANSWER;
  115. break;
  116. }
  117. break;
  118. case 'close_block':
  119. switch ($this->blockState) {
  120. case self::STATE_CLOSED:
  121. $this->rewriter->delete($index);
  122. break;
  123. case self::STATE_QUESTION:
  124. case self::STATE_ANSWER:
  125. $this->rewriter->insertBlockCall($index, 'close_block');
  126. $this->blockState = self::STATE_CLOSED;
  127. break;
  128. }
  129. break;
  130. }
  131. }
  132. /**
  133. * Wrap the last header
  134. */
  135. private function handlePluginQnaHeader($index) {
  136. /* On a clean install the distance between the header instruction and qna_header dummy
  137. sould be 2 (one section_open in between). Allowing distance to be in the range from
  138. 1 to 3 gives some flexibility for better compatibility with other plugins that might
  139. rearrange instructions around the header. */
  140. if (($index - $this->headerIndex) < 4) {
  141. $data[0] ='open';
  142. $data[1] = $this->headerTitle;
  143. $data[2] = end($this->headerId);
  144. $data[3] = $this->headerLevel;
  145. $this->rewriter->insertHeaderCall($this->headerIndex, $data);
  146. $this->rewriter->insertHeaderCall($this->headerIndex + 1, 'close');
  147. }
  148. $this->rewriter->delete($index);
  149. }
  150. /**
  151. *
  152. */
  153. public function beforeParserCacheUse($event, $param) {
  154. global $ID;
  155. $cache = $event->data;
  156. if (isset($cache->page) && ($cache->page == $ID)) {
  157. if (isset($cache->mode) && ($cache->mode == 'xhtml')) {
  158. $depends = p_get_metadata($ID, 'relation depends');
  159. if (!empty($depends) && isset($depends['rendering'])) {
  160. $this->addDependencies($cache, array_keys($depends['rendering']));
  161. }
  162. }
  163. }
  164. }
  165. /**
  166. * Add extra dependencies to the cache
  167. */
  168. private function addDependencies($cache, $depends) {
  169. foreach ($depends as $file) {
  170. if (!in_array($file, $cache->depends['files']) && file_exists($file)) {
  171. $cache->depends['files'][] = $file;
  172. }
  173. }
  174. }
  175. }
  176. class qna_instruction_rewriter {
  177. const DELETE = 1;
  178. const INSERT = 2;
  179. const SET_LEVEL = 3;
  180. private $correction;
  181. /**
  182. * Constructor
  183. */
  184. public function __construct() {
  185. $this->correction = array();
  186. }
  187. /**
  188. * Remove instruction at $index
  189. */
  190. public function delete($index) {
  191. $this->correction[$index][] = array(self::DELETE);
  192. }
  193. /**
  194. * Insert a plugin call in front of instruction at $index
  195. */
  196. public function insertPluginCall($index, $name, $data, $state, $text = '') {
  197. $this->correction[$index][] = array(self::INSERT, array('plugin', array($name, $data, $state, $text)));
  198. }
  199. /**
  200. * Insert qna_block plugin call in front of instruction at $index
  201. */
  202. public function insertBlockCall($index, $data, $repeat = 1) {
  203. for ($i = 0; $i < $repeat; $i++) {
  204. $this->insertPluginCall($index, 'qna_block', array($data), DOKU_LEXER_SPECIAL);
  205. }
  206. }
  207. /**
  208. * Insert qna_header plugin call in front of instruction at $index
  209. */
  210. public function insertHeaderCall($index, $data) {
  211. if (!is_array($data)) {
  212. $data = array($data);
  213. }
  214. $this->insertPluginCall($index, 'qna_header', $data, DOKU_LEXER_SPECIAL);
  215. }
  216. /**
  217. * Append a plugin call at the end of the instruction list
  218. */
  219. public function appendPluginCall($name, $data, $state, $text = '') {
  220. $this->correction[-1][] = array(self::INSERT, array('plugin', array($name, $data, $state, $text)));
  221. }
  222. /**
  223. * Append qna_block plugin call at the end of the instruction list
  224. */
  225. public function appendBlockCall($data, $repeat = 1) {
  226. for ($i = 0; $i < $repeat; $i++) {
  227. $this->appendPluginCall('qna_block', array($data), DOKU_LEXER_SPECIAL);
  228. }
  229. }
  230. /**
  231. * Set open_question list level for TOC
  232. */
  233. public function setQuestionLevel($index, $level) {
  234. $this->correction[$index][] = array(self::SET_LEVEL, $level);
  235. }
  236. /**
  237. * Apply the corrections
  238. */
  239. public function apply(&$instruction) {
  240. if (count($this->correction) > 0) {
  241. $index = $this->getCorrectionIndex();
  242. $corrections = count($index);
  243. $instructions = count($instruction);
  244. $output = array();
  245. for ($c = 0, $i = 0; $c < $corrections; $c++, $i++) {
  246. /* Copy all instructions, which are ahead of the next correction */
  247. for ( ; $i < $index[$c]; $i++) {
  248. $output[] = $instruction[$i];
  249. }
  250. $this->applyCorrections($i, $instruction, $output);
  251. }
  252. /* Copy the rest of instructions after the last correction */
  253. for ( ; $i < $instructions; $i++) {
  254. $output[] = $instruction[$i];
  255. }
  256. /* Handle appends */
  257. if (array_key_exists(-1, $this->correction)) {
  258. $this->applyAppend($output);
  259. }
  260. $instruction = $output;
  261. }
  262. }
  263. /**
  264. * Sort corrections on instruction index, remove appends
  265. */
  266. private function getCorrectionIndex() {
  267. $result = array_keys($this->correction);
  268. asort($result);
  269. $result = array_values($result);
  270. /* Remove appends */
  271. if ($result[0] == -1) {
  272. array_shift($result);
  273. }
  274. return $result;
  275. }
  276. /**
  277. * Apply corrections at $index
  278. */
  279. private function applyCorrections($index, $input, &$output) {
  280. $delete = false;
  281. $position = $input[$index][2];
  282. foreach ($this->correction[$index] as $correction) {
  283. switch ($correction[0]) {
  284. case self::DELETE:
  285. $delete = true;
  286. break;
  287. case self::INSERT:
  288. $output[] = array($correction[1][0], $correction[1][1], $position);
  289. break;
  290. case self::SET_LEVEL:
  291. if (($input[$index][0] == 'plugin') && ($input[$index][1][0] == 'qna_block') && ($input[$index][1][1][0] == 'open_question')) {
  292. $input[$index][1][1][3] = $correction[1];
  293. }
  294. break;
  295. }
  296. }
  297. if (!$delete) {
  298. $output[] = $input[$index];
  299. }
  300. }
  301. /**
  302. *
  303. */
  304. private function applyAppend(&$output) {
  305. $lastCall = end($output);
  306. $position = $lastCall[2];
  307. foreach ($this->correction[-1] as $correction) {
  308. switch ($correction[0]) {
  309. case self::INSERT:
  310. $output[] = array($correction[1][0], $correction[1][1], $position);
  311. break;
  312. }
  313. }
  314. }
  315. }