helper.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941
  1. <?php
  2. /**
  3. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  4. * @author Esther Brunner <wikidesign@gmail.com>
  5. * @author Christopher Smith <chris@jalakai.co.uk>
  6. * @author Gina Häußge, Michael Klier <dokuwiki@chimeric.de>
  7. * @author Michael Hamann <michael@content-space.de>
  8. */
  9. /**
  10. * Helper functions for the include plugin and other plugins that want to include pages.
  11. */
  12. class helper_plugin_include extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin
  13. var $defaults = array();
  14. var $sec_close = true;
  15. /** @var helper_plugin_tag $taghelper */
  16. var $taghelper = null;
  17. var $includes = array(); // deprecated - compatibility code for the blog plugin
  18. /**
  19. * Constructor loads default config settings once
  20. */
  21. function __construct() {
  22. $this->defaults['noheader'] = $this->getConf('noheader');
  23. $this->defaults['firstsec'] = $this->getConf('firstseconly');
  24. $this->defaults['editbtn'] = $this->getConf('showeditbtn');
  25. $this->defaults['taglogos'] = $this->getConf('showtaglogos');
  26. $this->defaults['footer'] = $this->getConf('showfooter');
  27. $this->defaults['redirect'] = $this->getConf('doredirect');
  28. $this->defaults['date'] = $this->getConf('showdate');
  29. $this->defaults['mdate'] = $this->getConf('showmdate');
  30. $this->defaults['user'] = $this->getConf('showuser');
  31. $this->defaults['comments'] = $this->getConf('showcomments');
  32. $this->defaults['linkbacks'] = $this->getConf('showlinkbacks');
  33. $this->defaults['tags'] = $this->getConf('showtags');
  34. $this->defaults['link'] = $this->getConf('showlink');
  35. $this->defaults['permalink'] = $this->getConf('showpermalink');
  36. $this->defaults['indent'] = $this->getConf('doindent');
  37. $this->defaults['linkonly'] = $this->getConf('linkonly');
  38. $this->defaults['title'] = $this->getConf('title');
  39. $this->defaults['pageexists'] = $this->getConf('pageexists');
  40. $this->defaults['parlink'] = $this->getConf('parlink');
  41. $this->defaults['inline'] = false;
  42. $this->defaults['order'] = $this->getConf('order');
  43. $this->defaults['rsort'] = $this->getConf('rsort');
  44. $this->defaults['depth'] = $this->getConf('depth');
  45. $this->defaults['readmore'] = $this->getConf('readmore');
  46. }
  47. /**
  48. * Available methods for other plugins
  49. */
  50. function getMethods() {
  51. $result = array();
  52. $result[] = array(
  53. 'name' => 'get_flags',
  54. 'desc' => 'overrides standard values for showfooter and firstseconly settings',
  55. 'params' => array('flags' => 'array'),
  56. );
  57. return $result;
  58. }
  59. /**
  60. * Overrides standard values for showfooter and firstseconly settings
  61. */
  62. function get_flags($setflags) {
  63. // load defaults
  64. $flags = $this->defaults;
  65. foreach ($setflags as $flag) {
  66. $value = '';
  67. if (strpos($flag, '=') !== false) {
  68. list($flag, $value) = explode('=', $flag, 2);
  69. }
  70. switch ($flag) {
  71. case 'footer':
  72. $flags['footer'] = 1;
  73. break;
  74. case 'nofooter':
  75. $flags['footer'] = 0;
  76. break;
  77. case 'firstseconly':
  78. case 'firstsectiononly':
  79. $flags['firstsec'] = 1;
  80. break;
  81. case 'fullpage':
  82. $flags['firstsec'] = 0;
  83. break;
  84. case 'showheader':
  85. case 'header':
  86. $flags['noheader'] = 0;
  87. break;
  88. case 'noheader':
  89. $flags['noheader'] = 1;
  90. break;
  91. case 'editbtn':
  92. case 'editbutton':
  93. $flags['editbtn'] = 1;
  94. break;
  95. case 'noeditbtn':
  96. case 'noeditbutton':
  97. $flags['editbtn'] = 0;
  98. break;
  99. case 'permalink':
  100. $flags['permalink'] = 1;
  101. break;
  102. case 'nopermalink':
  103. $flags['permalink'] = 0;
  104. break;
  105. case 'redirect':
  106. $flags['redirect'] = 1;
  107. break;
  108. case 'noredirect':
  109. $flags['redirect'] = 0;
  110. break;
  111. case 'link':
  112. $flags['link'] = 1;
  113. break;
  114. case 'nolink':
  115. $flags['link'] = 0;
  116. break;
  117. case 'user':
  118. $flags['user'] = 1;
  119. break;
  120. case 'nouser':
  121. $flags['user'] = 0;
  122. break;
  123. case 'comments':
  124. $flags['comments'] = 1;
  125. break;
  126. case 'nocomments':
  127. $flags['comments'] = 0;
  128. break;
  129. case 'linkbacks':
  130. $flags['linkbacks'] = 1;
  131. break;
  132. case 'nolinkbacks':
  133. $flags['linkbacks'] = 0;
  134. break;
  135. case 'tags':
  136. $flags['tags'] = 1;
  137. break;
  138. case 'notags':
  139. $flags['tags'] = 0;
  140. break;
  141. case 'date':
  142. $flags['date'] = 1;
  143. break;
  144. case 'nodate':
  145. $flags['date'] = 0;
  146. break;
  147. case 'mdate':
  148. $flags['mdate'] = 1;
  149. break;
  150. case 'nomdate':
  151. $flags['mdate'] = 0;
  152. break;
  153. case 'indent':
  154. $flags['indent'] = 1;
  155. break;
  156. case 'noindent':
  157. $flags['indent'] = 0;
  158. break;
  159. case 'linkonly':
  160. $flags['linkonly'] = 1;
  161. break;
  162. case 'nolinkonly':
  163. case 'include_content':
  164. $flags['linkonly'] = 0;
  165. break;
  166. case 'inline':
  167. $flags['inline'] = 1;
  168. break;
  169. case 'title':
  170. $flags['title'] = 1;
  171. break;
  172. case 'notitle':
  173. $flags['title'] = 0;
  174. break;
  175. case 'pageexists':
  176. $flags['pageexists'] = 1;
  177. break;
  178. case 'nopageexists':
  179. $flags['pageexists'] = 0;
  180. break;
  181. case 'existlink':
  182. $flags['pageexists'] = 1;
  183. $flags['linkonly'] = 1;
  184. break;
  185. case 'parlink':
  186. $flags['parlink'] = 1;
  187. break;
  188. case 'noparlink':
  189. $flags['parlink'] = 0;
  190. break;
  191. case 'order':
  192. $flags['order'] = $value;
  193. break;
  194. case 'sort':
  195. $flags['rsort'] = 0;
  196. break;
  197. case 'rsort':
  198. $flags['rsort'] = 1;
  199. break;
  200. case 'depth':
  201. $flags['depth'] = max(intval($value), 0);
  202. break;
  203. case 'beforeeach':
  204. $flags['beforeeach'] = $value;
  205. break;
  206. case 'aftereach':
  207. $flags['aftereach'] = $value;
  208. break;
  209. case 'readmore':
  210. $flags['readmore'] = 1;
  211. break;
  212. case 'noreadmore':
  213. $flags['readmore'] = 0;
  214. break;
  215. case 'exclude':
  216. $flags['exclude'] = $value;
  217. break;
  218. }
  219. }
  220. // the include_content URL parameter overrides flags
  221. if (isset($_REQUEST['include_content']))
  222. $flags['linkonly'] = 0;
  223. return $flags;
  224. }
  225. /**
  226. * Returns the converted instructions of a give page/section
  227. *
  228. * @author Michael Klier <chi@chimeric.de>
  229. * @author Michael Hamann <michael@content-space.de>
  230. */
  231. function _get_instructions($page, $sect, $mode, $lvl, $flags, $root_id = null, $included_pages = array()) {
  232. $key = ($sect) ? $page . '#' . $sect : $page;
  233. $this->includes[$key] = true; // legacy code for keeping compatibility with other plugins
  234. // keep compatibility with other plugins that don't know the $root_id parameter
  235. if (is_null($root_id)) {
  236. global $ID;
  237. $root_id = $ID;
  238. }
  239. if ($flags['linkonly']) {
  240. if (page_exists($page) || $flags['pageexists'] == 0) {
  241. $title = '';
  242. if ($flags['title'])
  243. $title = p_get_first_heading($page);
  244. if($flags['parlink']) {
  245. $ins = array(
  246. array('p_open', array()),
  247. array('internallink', array(':'.$key, $title)),
  248. array('p_close', array()),
  249. );
  250. } else {
  251. $ins = array(array('internallink', array(':'.$key,$title)));
  252. }
  253. }else {
  254. $ins = array();
  255. }
  256. } else {
  257. if (page_exists($page)) {
  258. global $ID;
  259. $backupID = $ID;
  260. $ID = $page; // Change the global $ID as otherwise plugins like the discussion plugin will save data for the wrong page
  261. $ins = p_cached_instructions(wikiFN($page), false, $page);
  262. $ID = $backupID;
  263. } else {
  264. $ins = array();
  265. }
  266. $this->_convert_instructions($ins, $lvl, $page, $sect, $flags, $root_id, $included_pages);
  267. }
  268. return $ins;
  269. }
  270. /**
  271. * Converts instructions of the included page
  272. *
  273. * The funcion iterates over the given list of instructions and generates
  274. * an index of header and section indicies. It also removes document
  275. * start/end instructions, converts links, and removes unwanted
  276. * instructions like tags, comments, linkbacks.
  277. *
  278. * Later all header/section levels are convertet to match the current
  279. * inclusion level.
  280. *
  281. * @author Michael Klier <chi@chimeric.de>
  282. */
  283. function _convert_instructions(&$ins, $lvl, $page, $sect, $flags, $root_id, $included_pages = array()) {
  284. global $conf;
  285. // filter instructions if needed
  286. if(!empty($sect)) {
  287. $this->_get_section($ins, $sect); // section required
  288. }
  289. if($flags['firstsec']) {
  290. $this->_get_firstsec($ins, $page, $flags); // only first section
  291. }
  292. $ns = getNS($page);
  293. $num = count($ins);
  294. $conv_idx = array(); // conversion index
  295. $lvl_max = false; // max level
  296. $first_header = -1;
  297. $no_header = false;
  298. $sect_title = false;
  299. $endpos = null; // end position of the raw wiki text
  300. $this->adapt_links($ins, $page, $included_pages);
  301. for($i=0; $i<$num; $i++) {
  302. switch($ins[$i][0]) {
  303. case 'document_start':
  304. case 'document_end':
  305. case 'section_edit':
  306. unset($ins[$i]);
  307. break;
  308. case 'header':
  309. // get section title of first section
  310. if($sect && !$sect_title) {
  311. $sect_title = $ins[$i][1][0];
  312. }
  313. // check if we need to skip the first header
  314. if((!$no_header) && $flags['noheader']) {
  315. $no_header = true;
  316. }
  317. $conv_idx[] = $i;
  318. // get index of first header
  319. if($first_header == -1) $first_header = $i;
  320. // get max level of this instructions set
  321. if(!$lvl_max || ($ins[$i][1][1] < $lvl_max)) {
  322. $lvl_max = $ins[$i][1][1];
  323. }
  324. break;
  325. case 'section_open':
  326. if ($flags['inline'])
  327. unset($ins[$i]);
  328. else
  329. $conv_idx[] = $i;
  330. break;
  331. case 'section_close':
  332. if ($flags['inline'])
  333. unset($ins[$i]);
  334. break;
  335. case 'nest':
  336. $this->adapt_links($ins[$i][1][0], $page, $included_pages);
  337. break;
  338. case 'plugin':
  339. // FIXME skip other plugins?
  340. switch($ins[$i][1][0]) {
  341. case 'tag_tag': // skip tags
  342. case 'discussion_comments': // skip comments
  343. case 'linkback': // skip linkbacks
  344. case 'data_entry': // skip data plugin
  345. case 'meta': // skip meta plugin
  346. case 'indexmenu_tag': // skip indexmenu sort tag
  347. case 'include_sorttag': // skip include plugin sort tag
  348. unset($ins[$i]);
  349. break;
  350. // adapt indentation level of nested includes
  351. case 'include_include':
  352. if (!$flags['inline'] && $flags['indent'])
  353. $ins[$i][1][1][4] += $lvl;
  354. break;
  355. /*
  356. * if there is already a closelastsecedit instruction (was added by one of the section
  357. * functions), store its position but delete it as it can't be determined yet if it is needed,
  358. * i.e. if there is a header which generates a section edit (depends on the levels, level
  359. * adjustments, $no_header, ...)
  360. */
  361. case 'include_closelastsecedit':
  362. $endpos = $ins[$i][1][1][0];
  363. unset($ins[$i]);
  364. break;
  365. }
  366. break;
  367. default:
  368. break;
  369. }
  370. }
  371. // calculate difference between header/section level and include level
  372. $diff = 0;
  373. if (!isset($lvl_max)) $lvl_max = 0; // if no level found in target, set to 0
  374. $diff = $lvl - $lvl_max + 1;
  375. if ($no_header) $diff -= 1; // push up one level if "noheader"
  376. // convert headers and set footer/permalink
  377. $hdr_deleted = false;
  378. $has_permalink = false;
  379. $footer_lvl = false;
  380. $contains_secedit = false;
  381. $section_close_at = false;
  382. foreach($conv_idx as $idx) {
  383. if($ins[$idx][0] == 'header') {
  384. if ($section_close_at === false && isset($ins[$idx+1]) && $ins[$idx+1][0] == 'section_open') {
  385. // store the index of the first heading that is followed by a new section
  386. // the wrap plugin creates sections without section_open so the section shouldn't be closed before them
  387. $section_close_at = $idx;
  388. }
  389. if($no_header && !$hdr_deleted) {
  390. unset ($ins[$idx]);
  391. $hdr_deleted = true;
  392. continue;
  393. }
  394. if($flags['indent']) {
  395. $lvl_new = (($ins[$idx][1][1] + $diff) > 5) ? 5 : ($ins[$idx][1][1] + $diff);
  396. $ins[$idx][1][1] = $lvl_new;
  397. }
  398. if($ins[$idx][1][1] <= $conf['maxseclevel'])
  399. $contains_secedit = true;
  400. // set permalink
  401. if($flags['link'] && !$has_permalink && ($idx == $first_header)) {
  402. $this->_permalink($ins[$idx], $page, $sect, $flags);
  403. $has_permalink = true;
  404. }
  405. // set footer level
  406. if(!$footer_lvl && ($idx == $first_header) && !$no_header) {
  407. if($flags['indent'] && isset($lvl_new)) {
  408. $footer_lvl = $lvl_new;
  409. } else {
  410. $footer_lvl = $lvl_max;
  411. }
  412. }
  413. } else {
  414. // it's a section
  415. if($flags['indent']) {
  416. $lvl_new = (($ins[$idx][1][0] + $diff) > 5) ? 5 : ($ins[$idx][1][0] + $diff);
  417. $ins[$idx][1][0] = $lvl_new;
  418. }
  419. // check if noheader is used and set the footer level to the first section
  420. if($no_header && !$footer_lvl) {
  421. if($flags['indent'] && isset($lvl_new)) {
  422. $footer_lvl = $lvl_new;
  423. } else {
  424. $footer_lvl = $lvl_max;
  425. }
  426. }
  427. }
  428. }
  429. // close last open section of the included page if there is any
  430. if ($contains_secedit) {
  431. array_push($ins, array('plugin', array('include_closelastsecedit', array($endpos))));
  432. }
  433. $include_secid = (isset($flags['include_secid']) ? $flags['include_secid'] : NULL);
  434. // add edit button
  435. if($flags['editbtn']) {
  436. $this->_editbtn($ins, $page, $sect, $sect_title, ($flags['redirect'] ? $root_id : false), $include_secid);
  437. }
  438. // add footer
  439. if($flags['footer']) {
  440. $ins[] = $this->_footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id);
  441. }
  442. // wrap content at the beginning of the include that is not in a section in a section
  443. if ($lvl > 0 && $section_close_at !== 0 && $flags['indent'] && !$flags['inline']) {
  444. if ($section_close_at === false) {
  445. $ins[] = array('section_close', array());
  446. array_unshift($ins, array('section_open', array($lvl)));
  447. } else {
  448. $section_close_idx = array_search($section_close_at, array_keys($ins));
  449. if ($section_close_idx > 0) {
  450. $before_ins = array_slice($ins, 0, $section_close_idx);
  451. $after_ins = array_slice($ins, $section_close_idx);
  452. $ins = array_merge($before_ins, array(array('section_close', array())), $after_ins);
  453. array_unshift($ins, array('section_open', array($lvl)));
  454. }
  455. }
  456. }
  457. // add instructions entry wrapper
  458. array_unshift($ins, array('plugin', array('include_wrap', array('open', $page, $flags['redirect'], $include_secid))));
  459. if (isset($flags['beforeeach']))
  460. array_unshift($ins, array('entity', array($flags['beforeeach'])));
  461. array_push($ins, array('plugin', array('include_wrap', array('close'))));
  462. if (isset($flags['aftereach']))
  463. array_push($ins, array('entity', array($flags['aftereach'])));
  464. // close previous section if any and re-open after inclusion
  465. if($lvl != 0 && $this->sec_close && !$flags['inline']) {
  466. array_unshift($ins, array('section_close', array()));
  467. $ins[] = array('section_open', array($lvl));
  468. }
  469. }
  470. /**
  471. * Appends instruction item for the include plugin footer
  472. *
  473. * @author Michael Klier <chi@chimeric.de>
  474. */
  475. function _footer($page, $sect, $sect_title, $flags, $footer_lvl, $root_id) {
  476. $footer = array();
  477. $footer[0] = 'plugin';
  478. $footer[1] = array('include_footer', array($page, $sect, $sect_title, $flags, $root_id, $footer_lvl));
  479. return $footer;
  480. }
  481. /**
  482. * Appends instruction item for an edit button
  483. *
  484. * @author Michael Klier <chi@chimeric.de>
  485. */
  486. function _editbtn(&$ins, $page, $sect, $sect_title, $root_id, $hid = '') {
  487. $title = ($sect) ? $sect_title : $page;
  488. $editbtn = array();
  489. $editbtn[0] = 'plugin';
  490. $editbtn[1] = array('include_editbtn', array($title, $hid));
  491. $ins[] = $editbtn;
  492. }
  493. /**
  494. * Convert instruction item for a permalink header
  495. *
  496. * @author Michael Klier <chi@chimeric.de>
  497. */
  498. function _permalink(&$ins, $page, $sect, $flags) {
  499. $ins[0] = 'plugin';
  500. $ins[1] = array('include_header', array($ins[1][0], $ins[1][1], $ins[1][2], $page, $sect, $flags));
  501. }
  502. /**
  503. * Convert internal and local links depending on the included pages
  504. *
  505. * @param array $ins The instructions that shall be adapted
  506. * @param string $page The included page
  507. * @param array $included_pages The array of pages that are included
  508. */
  509. private function adapt_links(&$ins, $page, $included_pages = null) {
  510. $num = count($ins);
  511. $ns = getNS($page);
  512. for($i=0; $i<$num; $i++) {
  513. // adjust links with image titles
  514. if (strpos($ins[$i][0], 'link') !== false && isset($ins[$i][1][1]) && is_array($ins[$i][1][1]) && $ins[$i][1][1]['type'] == 'internalmedia') {
  515. // resolve relative ids, but without cleaning in order to preserve the name
  516. $media_id = resolve_id($ns, $ins[$i][1][1]['src']);
  517. // make sure that after resolving the link again it will be the same link
  518. if ($media_id[0] != ':') $media_id = ':'.$media_id;
  519. $ins[$i][1][1]['src'] = $media_id;
  520. }
  521. switch($ins[$i][0]) {
  522. case 'internallink':
  523. case 'internalmedia':
  524. // make sure parameters aren't touched
  525. $link_params = '';
  526. $link_id = $ins[$i][1][0];
  527. $link_parts = explode('?', $link_id, 2);
  528. if (count($link_parts) === 2) {
  529. $link_id = $link_parts[0];
  530. $link_params = $link_parts[1];
  531. }
  532. // resolve the id without cleaning it
  533. $link_id = resolve_id($ns, $link_id, false);
  534. // this id is internal (i.e. absolute) now, add ':' to make resolve_id work again
  535. if ($link_id[0] != ':') $link_id = ':'.$link_id;
  536. // restore parameters
  537. $ins[$i][1][0] = ($link_params != '') ? $link_id.'?'.$link_params : $link_id;
  538. if ($ins[$i][0] == 'internallink' && !empty($included_pages)) {
  539. // change links to included pages into local links
  540. // only adapt links without parameters
  541. $link_id = $ins[$i][1][0];
  542. $link_parts = explode('?', $link_id, 2);
  543. if (count($link_parts) === 1) {
  544. $exists = false;
  545. resolve_pageid($ns, $link_id, $exists);
  546. $link_parts = explode('#', $link_id, 2);
  547. $hash = '';
  548. if (count($link_parts) === 2) {
  549. list($link_id, $hash) = $link_parts;
  550. }
  551. if (array_key_exists($link_id, $included_pages)) {
  552. if ($hash) {
  553. // hopefully the hash is also unique in the including page (otherwise this might be the wrong link target)
  554. $ins[$i][0] = 'locallink';
  555. $ins[$i][1][0] = $hash;
  556. } else {
  557. // the include section ids are different from normal section ids (so they won't conflict) but this
  558. // also means that the normal locallink function can't be used
  559. $ins[$i][0] = 'plugin';
  560. $ins[$i][1] = array('include_locallink', array($included_pages[$link_id]['hid'], $ins[$i][1][1], $ins[$i][1][0]));
  561. }
  562. }
  563. }
  564. }
  565. break;
  566. case 'locallink':
  567. /* Convert local links to internal links if the page hasn't been fully included */
  568. if ($included_pages == null || !array_key_exists($page, $included_pages)) {
  569. $ins[$i][0] = 'internallink';
  570. $ins[$i][1][0] = ':'.$page.'#'.$ins[$i][1][0];
  571. }
  572. break;
  573. }
  574. }
  575. }
  576. /**
  577. * Get a section including its subsections
  578. *
  579. * @author Michael Klier <chi@chimeric.de>
  580. */
  581. function _get_section(&$ins, $sect) {
  582. $num = count($ins);
  583. $offset = false;
  584. $lvl = false;
  585. $end = false;
  586. $endpos = null; // end position in the input text, needed for section edit buttons
  587. $check = array(); // used for sectionID() in order to get the same ids as the xhtml renderer
  588. for($i=0; $i<$num; $i++) {
  589. if ($ins[$i][0] == 'header') {
  590. // found the right header
  591. if (sectionID($ins[$i][1][0], $check) == $sect) {
  592. $offset = $i;
  593. $lvl = $ins[$i][1][1];
  594. } elseif ($offset && $lvl && ($ins[$i][1][1] <= $lvl)) {
  595. $end = $i - $offset;
  596. $endpos = $ins[$i][1][2]; // the position directly after the found section, needed for the section edit button
  597. break;
  598. }
  599. }
  600. }
  601. $offset = $offset ? $offset : 0;
  602. $end = $end ? $end : ($num - 1);
  603. if(is_array($ins)) {
  604. $ins = array_slice($ins, $offset, $end);
  605. // store the end position in the include_closelastsecedit instruction so it can generate a matching button
  606. $ins[] = array('plugin', array('include_closelastsecedit', array($endpos)));
  607. }
  608. }
  609. /**
  610. * Only display the first section of a page and a readmore link
  611. *
  612. * @author Michael Klier <chi@chimeric.de>
  613. */
  614. function _get_firstsec(&$ins, $page, $flags) {
  615. $num = count($ins);
  616. $first_sect = false;
  617. $endpos = null; // end position in the input text
  618. for($i=0; $i<$num; $i++) {
  619. if($ins[$i][0] == 'section_close') {
  620. $first_sect = $i;
  621. }
  622. if ($ins[$i][0] == 'header') {
  623. /*
  624. * Store the position of the last header that is encountered. As section_close/open-instruction are
  625. * always (unless some plugin modifies this) around a header instruction this means that the last
  626. * position that is stored here is exactly the position of the section_close/open at which the content
  627. * is truncated.
  628. */
  629. $endpos = $ins[$i][1][2];
  630. }
  631. // only truncate the content and add the read more link when there is really
  632. // more than that first section
  633. if(($first_sect) && ($ins[$i][0] == 'section_open')) {
  634. $ins = array_slice($ins, 0, $first_sect);
  635. if ($flags['readmore']) {
  636. $ins[] = array('plugin', array('include_readmore', array($page)));
  637. }
  638. $ins[] = array('section_close', array());
  639. // store the end position in the include_closelastsecedit instruction so it can generate a matching button
  640. $ins[] = array('plugin', array('include_closelastsecedit', array($endpos)));
  641. return;
  642. }
  643. }
  644. }
  645. /**
  646. * Gives a list of pages for a given include statement
  647. *
  648. * @author Michael Hamann <michael@content-space.de>
  649. */
  650. function _get_included_pages($mode, $page, $sect, $parent_id, $flags) {
  651. global $conf;
  652. $pages = array();
  653. switch($mode) {
  654. case 'namespace':
  655. $page = cleanID($page);
  656. $ns = utf8_encodeFN(str_replace(':', '/', $page));
  657. // depth is absolute depth, not relative depth, but 0 has a special meaning.
  658. $depth = $flags['depth'] ? $flags['depth'] + substr_count($page, ':') + ($page ? 1 : 0) : 0;
  659. search($pagearrays, $conf['datadir'], 'search_allpages', array('depth' => $depth, 'skipacl' => false), $ns);
  660. if (is_array($pagearrays)) {
  661. foreach ($pagearrays as $pagearray) {
  662. if (!isHiddenPage($pagearray['id'])) // skip hidden pages
  663. $pages[] = $pagearray['id'];
  664. }
  665. }
  666. break;
  667. case 'tagtopic':
  668. if (!$this->taghelper)
  669. $this->taghelper = plugin_load('helper', 'tag');
  670. if(!$this->taghelper) {
  671. msg('You have to install the tag plugin to use this functionality!', -1);
  672. return array();
  673. }
  674. $tag = $page;
  675. $sect = '';
  676. $pagearrays = $this->taghelper->getTopic('', null, $tag);
  677. foreach ($pagearrays as $pagearray) {
  678. $pages[] = $pagearray['id'];
  679. }
  680. break;
  681. default:
  682. $page = $this->_apply_macro($page, $parent_id);
  683. resolve_pageid(getNS($parent_id), $page, $exists); // resolve shortcuts and clean ID
  684. if (auth_quickaclcheck($page) >= AUTH_READ)
  685. $pages[] = $page;
  686. }
  687. if (isset($flags['exclude']))
  688. $pages = array_filter($pages, function ($page) use ($flags) {
  689. if (@preg_match($flags['exclude'], $page))
  690. return FALSE;
  691. return TRUE;
  692. });
  693. if (count($pages) > 1) {
  694. if ($flags['order'] === 'id') {
  695. if ($flags['rsort']) {
  696. usort($pages, array($this, '_r_strnatcasecmp'));
  697. } else {
  698. natcasesort($pages);
  699. }
  700. } else {
  701. $ordered_pages = array();
  702. foreach ($pages as $page) {
  703. $key = '';
  704. switch ($flags['order']) {
  705. case 'title':
  706. $key = p_get_first_heading($page);
  707. break;
  708. case 'created':
  709. $key = p_get_metadata($page, 'date created', METADATA_DONT_RENDER);
  710. break;
  711. case 'modified':
  712. $key = p_get_metadata($page, 'date modified', METADATA_DONT_RENDER);
  713. break;
  714. case 'indexmenu':
  715. $key = p_get_metadata($page, 'indexmenu_n', METADATA_RENDER_USING_SIMPLE_CACHE);
  716. if ($key === null)
  717. $key = '';
  718. break;
  719. case 'custom':
  720. $key = p_get_metadata($page, 'include_n', METADATA_RENDER_USING_SIMPLE_CACHE);
  721. if ($key === null)
  722. $key = '';
  723. break;
  724. }
  725. $key .= '_'.$page;
  726. $ordered_pages[$key] = $page;
  727. }
  728. if ($flags['rsort']) {
  729. uksort($ordered_pages, array($this, '_r_strnatcasecmp'));
  730. } else {
  731. uksort($ordered_pages, 'strnatcasecmp');
  732. }
  733. $pages = $ordered_pages;
  734. }
  735. }
  736. $result = array();
  737. foreach ($pages as $page) {
  738. $exists = page_exists($page);
  739. $result[] = array('id' => $page, 'exists' => $exists, 'parent_id' => $parent_id);
  740. }
  741. return $result;
  742. }
  743. /**
  744. * String comparisons using a "natural order" algorithm in reverse order
  745. *
  746. * @link http://php.net/manual/en/function.strnatcmp.php
  747. * @param string $a First string
  748. * @param string $b Second string
  749. * @return int Similar to other string comparison functions, this one returns &lt; 0 if
  750. * str1 is greater than str2; &gt;
  751. * 0 if str1 is lesser than
  752. * str2, and 0 if they are equal.
  753. */
  754. function _r_strnatcasecmp($a, $b) {
  755. return strnatcasecmp($b, $a);
  756. }
  757. /**
  758. * This function generates the list of all included pages from a list of metadata
  759. * instructions.
  760. */
  761. function _get_included_pages_from_meta_instructions($instructions) {
  762. $pages = array();
  763. foreach ($instructions as $instruction) {
  764. $mode = $instruction['mode'];
  765. $page = $instruction['page'];
  766. $sect = $instruction['sect'];
  767. $parent_id = $instruction['parent_id'];
  768. $flags = $instruction['flags'];
  769. $pages = array_merge($pages, $this->_get_included_pages($mode, $page, $sect, $parent_id, $flags));
  770. }
  771. return $pages;
  772. }
  773. /**
  774. * Get wiki language from "HTTP_ACCEPT_LANGUAGE"
  775. * We allow the pattern e.g. "ja,en-US;q=0.7,en;q=0.3"
  776. */
  777. function _get_language_of_wiki($id, $parent_id) {
  778. global $conf;
  779. $result = $conf['lang'];
  780. if(strpos($id, '@BROWSER_LANG@') !== false){
  781. $brlangp = "/([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})*|\*)(;q=(0(.[0-9]{0,3})?|1(.0{0,3})?))?/";
  782. if(preg_match_all(
  783. $brlangp, $_SERVER["HTTP_ACCEPT_LANGUAGE"],
  784. $matches, PREG_SET_ORDER
  785. )){
  786. $langs = array();
  787. foreach($matches as $match){
  788. $langname = $match[1] == '*' ? $conf['lang'] : $match[1];
  789. $qvalue = $match[4] == '' ? 1.0 : $match[4];
  790. $langs[$langname] = $qvalue;
  791. }
  792. arsort($langs);
  793. foreach($langs as $lang => $langq){
  794. $testpage = $this->_apply_macro(str_replace('@BROWSER_LANG@', $lang, $id), $parent_id);
  795. resolve_pageid(getNS($parent_id), $testpage, $exists);
  796. if($exists){
  797. $result = $lang;
  798. break;
  799. }
  800. }
  801. }
  802. }
  803. return cleanID($result);
  804. }
  805. /**
  806. * Makes user or date dependent includes possible
  807. */
  808. function _apply_macro($id, $parent_id) {
  809. global $USERINFO;
  810. /* @var Input $INPUT */
  811. global $INPUT;
  812. // The following is basicaly copied from basicinfo() because
  813. // this function can be called from within pageinfo() in
  814. // p_get_metadata and thus we cannot rely on $INFO being set
  815. if($INPUT->server->has('REMOTE_USER')) {
  816. $user = $INPUT->server->str('REMOTE_USER');
  817. } else {
  818. // no registered user - use IP
  819. $user = clientIP(true);
  820. }
  821. // Take user's name if possible, login name otherwise
  822. if (!empty($USERINFO['name'])) {
  823. $name = $USERINFO['name'];
  824. } else {
  825. $name = $user;
  826. }
  827. // Take first group if possible
  828. if (!empty($USERINFO['grps'])) {
  829. $group = $USERINFO['grps'][0];
  830. } else {
  831. $group = 'ALL';
  832. }
  833. $time_stamp = time();
  834. if(preg_match('/@DATE(\w+)@/',$id,$matches)) {
  835. switch($matches[1]) {
  836. case 'PMONTH':
  837. $time_stamp = strtotime("-1 month");
  838. break;
  839. case 'NMONTH':
  840. $time_stamp = strtotime("+1 month");
  841. break;
  842. case 'NWEEK':
  843. $time_stamp = strtotime("+1 week");
  844. break;
  845. case 'PWEEK':
  846. $time_stamp = strtotime("-1 week");
  847. break;
  848. case 'TOMORROW':
  849. $time_stamp = strtotime("+1 day");
  850. break;
  851. case 'YESTERDAY':
  852. $time_stamp = strtotime("-1 day");
  853. break;
  854. case 'NYEAR':
  855. $time_stamp = strtotime("+1 year");
  856. break;
  857. case 'PYEAR':
  858. $time_stamp = strtotime("-1 year");
  859. break;
  860. }
  861. $id = preg_replace('/@DATE(\w+)@/','', $id);
  862. }
  863. $replace = array(
  864. '@USER@' => cleanID($user),
  865. '@NAME@' => cleanID($name),
  866. '@GROUP@' => cleanID($group),
  867. '@BROWSER_LANG@' => $this->_get_language_of_wiki($id, $parent_id),
  868. '@YEAR@' => date('Y',$time_stamp),
  869. '@MONTH@' => date('m',$time_stamp),
  870. '@WEEK@' => date('W',$time_stamp),
  871. '@DAY@' => date('d',$time_stamp),
  872. '@YEARPMONTH@' => date('Ym',strtotime("-1 month")),
  873. '@PMONTH@' => date('m',strtotime("-1 month")),
  874. '@NMONTH@' => date('m',strtotime("+1 month")),
  875. '@YEARNMONTH@' => date('Ym',strtotime("+1 month")),
  876. '@YEARPWEEK@' => date('YW',strtotime("-1 week")),
  877. '@PWEEK@' => date('W',strtotime("-1 week")),
  878. '@NWEEK@' => date('W',strtotime("+1 week")),
  879. '@YEARNWEEK@' => date('YW',strtotime("+1 week")),
  880. );
  881. return str_replace(array_keys($replace), array_values($replace), $id);
  882. }
  883. }
  884. // vim:ts=4:sw=4:et: