pageutils.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. <?php
  2. /**
  3. * Utilities for handling pagenames
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Andreas Gohr <andi@splitbrain.org>
  7. * @todo Combine similar functions like {wiki,media,meta}FN()
  8. */
  9. use dokuwiki\ChangeLog\MediaChangeLog;
  10. use dokuwiki\ChangeLog\PageChangeLog;
  11. use dokuwiki\File\MediaResolver;
  12. use dokuwiki\File\PageResolver;
  13. /**
  14. * Fetch the an ID from request
  15. *
  16. * Uses either standard $_REQUEST variable or extracts it from
  17. * the full request URI when userewrite is set to 2
  18. *
  19. * For $param='id' $conf['start'] is returned if no id was found.
  20. * If the second parameter is true (default) the ID is cleaned.
  21. *
  22. * @author Andreas Gohr <andi@splitbrain.org>
  23. *
  24. * @param string $param the $_REQUEST variable name, default 'id'
  25. * @param bool $clean if true, ID is cleaned
  26. * @return string
  27. */
  28. function getID($param = 'id', $clean = true)
  29. {
  30. /** @var Input $INPUT */
  31. global $INPUT;
  32. global $conf;
  33. global $ACT;
  34. $id = $INPUT->str($param);
  35. //construct page id from request URI
  36. if (empty($id) && $conf['userewrite'] == 2) {
  37. $request = $INPUT->server->str('REQUEST_URI');
  38. $script = '';
  39. //get the script URL
  40. if ($conf['basedir']) {
  41. $relpath = '';
  42. if ($param != 'id') {
  43. $relpath = 'lib/exe/';
  44. }
  45. $script = $conf['basedir'] . $relpath .
  46. \dokuwiki\Utf8\PhpString::basename($INPUT->server->str('SCRIPT_FILENAME'));
  47. } elseif ($INPUT->server->str('PATH_INFO')) {
  48. $request = $INPUT->server->str('PATH_INFO');
  49. } elseif ($INPUT->server->str('SCRIPT_NAME')) {
  50. $script = $INPUT->server->str('SCRIPT_NAME');
  51. } elseif ($INPUT->server->str('DOCUMENT_ROOT') && $INPUT->server->str('SCRIPT_FILENAME')) {
  52. $script = preg_replace(
  53. '/^' . preg_quote($INPUT->server->str('DOCUMENT_ROOT'), '/') . '/',
  54. '',
  55. $INPUT->server->str('SCRIPT_FILENAME')
  56. );
  57. $script = '/' . $script;
  58. }
  59. //clean script and request (fixes a windows problem)
  60. $script = preg_replace('/\/\/+/', '/', $script);
  61. $request = preg_replace('/\/\/+/', '/', $request);
  62. //remove script URL and Querystring to gain the id
  63. if (preg_match('/^' . preg_quote($script, '/') . '(.*)/', $request, $match)) {
  64. $id = preg_replace('/\?.*/', '', $match[1]);
  65. }
  66. $id = urldecode($id);
  67. //strip leading slashes
  68. $id = preg_replace('!^/+!', '', $id);
  69. }
  70. // Namespace autolinking from URL
  71. if (substr($id, -1) == ':' || ($conf['useslash'] && substr($id, -1) == '/')) {
  72. if (page_exists($id . $conf['start'])) {
  73. // start page inside namespace
  74. $id = $id . $conf['start'];
  75. } elseif (page_exists($id . noNS(cleanID($id)))) {
  76. // page named like the NS inside the NS
  77. $id = $id . noNS(cleanID($id));
  78. } elseif (page_exists($id)) {
  79. // page like namespace exists
  80. $id = substr($id, 0, -1);
  81. } else {
  82. // fall back to default
  83. $id = $id . $conf['start'];
  84. }
  85. if (isset($ACT) && $ACT === 'show') {
  86. $urlParameters = $_GET;
  87. if (isset($urlParameters['id'])) {
  88. unset($urlParameters['id']);
  89. }
  90. send_redirect(wl($id, $urlParameters, true, '&'));
  91. }
  92. }
  93. if ($clean) $id = cleanID($id);
  94. if ($id === '' && $param == 'id') $id = $conf['start'];
  95. return $id;
  96. }
  97. /**
  98. * Remove unwanted chars from ID
  99. *
  100. * Cleans a given ID to only use allowed characters. Accented characters are
  101. * converted to unaccented ones
  102. *
  103. * @author Andreas Gohr <andi@splitbrain.org>
  104. *
  105. * @param string $raw_id The pageid to clean
  106. * @param boolean $ascii Force ASCII
  107. * @return string cleaned id
  108. */
  109. function cleanID($raw_id, $ascii = false)
  110. {
  111. global $conf;
  112. static $sepcharpat = null;
  113. global $cache_cleanid;
  114. $cache = & $cache_cleanid;
  115. // check if it's already in the memory cache
  116. if (!$ascii && isset($cache[(string)$raw_id])) {
  117. return $cache[(string)$raw_id];
  118. }
  119. $sepchar = $conf['sepchar'];
  120. if ($sepcharpat == null) // build string only once to save clock cycles
  121. $sepcharpat = '#\\' . $sepchar . '+#';
  122. $id = trim((string)$raw_id);
  123. $id = \dokuwiki\Utf8\PhpString::strtolower($id);
  124. //alternative namespace seperator
  125. if ($conf['useslash']) {
  126. $id = strtr($id, ';/', '::');
  127. } else {
  128. $id = strtr($id, ';/', ':' . $sepchar);
  129. }
  130. if ($conf['deaccent'] == 2 || $ascii) $id = \dokuwiki\Utf8\Clean::romanize($id);
  131. if ($conf['deaccent'] || $ascii) $id = \dokuwiki\Utf8\Clean::deaccent($id, -1);
  132. //remove specials
  133. $id = \dokuwiki\Utf8\Clean::stripspecials($id, $sepchar, '\*');
  134. if ($ascii) $id = \dokuwiki\Utf8\Clean::strip($id);
  135. //clean up
  136. $id = preg_replace($sepcharpat, $sepchar, $id);
  137. $id = preg_replace('#:+#', ':', $id);
  138. $id = trim($id, ':._-');
  139. $id = preg_replace('#:[:\._\-]+#', ':', $id);
  140. $id = preg_replace('#[:\._\-]+:#', ':', $id);
  141. if (!$ascii) $cache[(string)$raw_id] = $id;
  142. return($id);
  143. }
  144. /**
  145. * Return namespacepart of a wiki ID
  146. *
  147. * @author Andreas Gohr <andi@splitbrain.org>
  148. *
  149. * @param string $id
  150. * @return string|false the namespace part or false if the given ID has no namespace (root)
  151. */
  152. function getNS($id)
  153. {
  154. $pos = strrpos((string)$id, ':');
  155. if ($pos !== false) {
  156. return substr((string)$id, 0, $pos);
  157. }
  158. return false;
  159. }
  160. /**
  161. * Returns the ID without the namespace
  162. *
  163. * @author Andreas Gohr <andi@splitbrain.org>
  164. *
  165. * @param string $id
  166. * @return string
  167. */
  168. function noNS($id)
  169. {
  170. $pos = strrpos($id, ':');
  171. if ($pos !== false) {
  172. return substr($id, $pos + 1);
  173. } else {
  174. return $id;
  175. }
  176. }
  177. /**
  178. * Returns the current namespace
  179. *
  180. * @author Nathan Fritz <fritzn@crown.edu>
  181. *
  182. * @param string $id
  183. * @return string
  184. */
  185. function curNS($id)
  186. {
  187. return noNS(getNS($id));
  188. }
  189. /**
  190. * Returns the ID without the namespace or current namespace for 'start' pages
  191. *
  192. * @author Nathan Fritz <fritzn@crown.edu>
  193. *
  194. * @param string $id
  195. * @return string
  196. */
  197. function noNSorNS($id)
  198. {
  199. global $conf;
  200. $p = noNS($id);
  201. if ($p === $conf['start'] || $p === false || $p === '') {
  202. $p = curNS($id);
  203. if ($p === false || $p === '') {
  204. return $conf['start'];
  205. }
  206. }
  207. return $p;
  208. }
  209. /**
  210. * Creates a XHTML valid linkid from a given headline title
  211. *
  212. * @param string $title The headline title
  213. * @param array|bool $check Existing IDs
  214. * @return string the title
  215. *
  216. * @author Andreas Gohr <andi@splitbrain.org>
  217. */
  218. function sectionID($title, &$check)
  219. {
  220. $title = str_replace(array(':','.'), '', cleanID($title));
  221. $new = ltrim($title, '0123456789_-');
  222. if (empty($new)) {
  223. $title = 'section' . preg_replace('/[^0-9]+/', '', $title); //keep numbers from headline
  224. } else {
  225. $title = $new;
  226. }
  227. if (is_array($check)) {
  228. $suffix = 0;
  229. $candidateTitle = $title;
  230. while (in_array($candidateTitle, $check)) {
  231. $candidateTitle = $title . ++$suffix;
  232. }
  233. $check [] = $candidateTitle;
  234. return $candidateTitle;
  235. } else {
  236. return $title;
  237. }
  238. }
  239. /**
  240. * Wiki page existence check
  241. *
  242. * parameters as for wikiFN
  243. *
  244. * @author Chris Smith <chris@jalakai.co.uk>
  245. *
  246. * @param string $id page id
  247. * @param string|int $rev empty or revision timestamp
  248. * @param bool $clean flag indicating that $id should be cleaned (see wikiFN as well)
  249. * @param bool $date_at
  250. * @return bool exists?
  251. */
  252. function page_exists($id, $rev = '', $clean = true, $date_at = false)
  253. {
  254. $id = (explode('#', $id, 2))[0]; // #3608
  255. if ($rev !== '' && $date_at) {
  256. $pagelog = new PageChangeLog($id);
  257. $pagelog_rev = $pagelog->getLastRevisionAt($rev);
  258. if ($pagelog_rev !== false)
  259. $rev = $pagelog_rev;
  260. }
  261. return file_exists(wikiFN($id, $rev, $clean));
  262. }
  263. /**
  264. * Media existence check
  265. *
  266. * @param string $id page id
  267. * @param string|int $rev empty or revision timestamp
  268. * @param bool $clean flag indicating that $id should be cleaned (see mediaFN as well)
  269. * @param bool $date_at
  270. * @return bool exists?
  271. */
  272. function media_exists($id, $rev = '', $clean = true, $date_at = false)
  273. {
  274. if ($rev !== '' && $date_at) {
  275. $changeLog = new MediaChangeLog($id);
  276. $changelog_rev = $changeLog->getLastRevisionAt($rev);
  277. if ($changelog_rev !== false) {
  278. $rev = $changelog_rev;
  279. }
  280. }
  281. return file_exists(mediaFN($id, $rev, $clean));
  282. }
  283. /**
  284. * returns the full path to the datafile specified by ID and optional revision
  285. *
  286. * The filename is URL encoded to protect Unicode chars
  287. *
  288. * @param $raw_id string id of wikipage
  289. * @param $rev int|string page revision, empty string for current
  290. * @param $clean bool flag indicating that $raw_id should be cleaned. Only set to false
  291. * when $id is guaranteed to have been cleaned already.
  292. * @return string full path
  293. *
  294. * @author Andreas Gohr <andi@splitbrain.org>
  295. */
  296. function wikiFN($raw_id, $rev = '', $clean = true)
  297. {
  298. global $conf;
  299. global $cache_wikifn;
  300. $cache = & $cache_wikifn;
  301. $id = $raw_id;
  302. if ($clean) $id = cleanID($id);
  303. $id = str_replace(':', '/', $id);
  304. if (isset($cache[$id]) && isset($cache[$id][$rev])) {
  305. return $cache[$id][$rev];
  306. }
  307. if (empty($rev)) {
  308. $fn = $conf['datadir'] . '/' . utf8_encodeFN($id) . '.txt';
  309. } else {
  310. $fn = $conf['olddir'] . '/' . utf8_encodeFN($id) . '.' . $rev . '.txt';
  311. if ($conf['compression']) {
  312. //test for extensions here, we want to read both compressions
  313. if (file_exists($fn . '.gz')) {
  314. $fn .= '.gz';
  315. } elseif (file_exists($fn . '.bz2')) {
  316. $fn .= '.bz2';
  317. } else {
  318. //file doesnt exist yet, so we take the configured extension
  319. $fn .= '.' . $conf['compression'];
  320. }
  321. }
  322. }
  323. if (!isset($cache[$id])) {
  324. $cache[$id] = array();
  325. }
  326. $cache[$id][$rev] = $fn;
  327. return $fn;
  328. }
  329. /**
  330. * Returns the full path to the file for locking the page while editing.
  331. *
  332. * @author Ben Coburn <btcoburn@silicodon.net>
  333. *
  334. * @param string $id page id
  335. * @return string full path
  336. */
  337. function wikiLockFN($id)
  338. {
  339. global $conf;
  340. return $conf['lockdir'] . '/' . md5(cleanID($id)) . '.lock';
  341. }
  342. /**
  343. * returns the full path to the meta file specified by ID and extension
  344. *
  345. * @author Steven Danz <steven-danz@kc.rr.com>
  346. *
  347. * @param string $id page id
  348. * @param string $ext file extension
  349. * @return string full path
  350. */
  351. function metaFN($id, $ext)
  352. {
  353. global $conf;
  354. $id = cleanID($id);
  355. $id = str_replace(':', '/', $id);
  356. $fn = $conf['metadir'] . '/' . utf8_encodeFN($id) . $ext;
  357. return $fn;
  358. }
  359. /**
  360. * returns the full path to the media's meta file specified by ID and extension
  361. *
  362. * @author Kate Arzamastseva <pshns@ukr.net>
  363. *
  364. * @param string $id media id
  365. * @param string $ext extension of media
  366. * @return string
  367. */
  368. function mediaMetaFN($id, $ext)
  369. {
  370. global $conf;
  371. $id = cleanID($id);
  372. $id = str_replace(':', '/', $id);
  373. $fn = $conf['mediametadir'] . '/' . utf8_encodeFN($id) . $ext;
  374. return $fn;
  375. }
  376. /**
  377. * returns an array of full paths to all metafiles of a given ID
  378. *
  379. * @author Esther Brunner <esther@kaffeehaus.ch>
  380. * @author Michael Hamann <michael@content-space.de>
  381. *
  382. * @param string $id page id
  383. * @return array
  384. */
  385. function metaFiles($id)
  386. {
  387. $basename = metaFN($id, '');
  388. $files = glob($basename . '.*', GLOB_MARK);
  389. // filter files like foo.bar.meta when $id == 'foo'
  390. return $files ? preg_grep('/^' . preg_quote($basename, '/') . '\.[^.\/]*$/u', $files) : array();
  391. }
  392. /**
  393. * returns the full path to the mediafile specified by ID
  394. *
  395. * The filename is URL encoded to protect Unicode chars
  396. *
  397. * @author Andreas Gohr <andi@splitbrain.org>
  398. * @author Kate Arzamastseva <pshns@ukr.net>
  399. *
  400. * @param string $id media id
  401. * @param string|int $rev empty string or revision timestamp
  402. * @param bool $clean
  403. *
  404. * @return string full path
  405. */
  406. function mediaFN($id, $rev = '', $clean = true)
  407. {
  408. global $conf;
  409. if ($clean) $id = cleanID($id);
  410. $id = str_replace(':', '/', $id);
  411. if (empty($rev)) {
  412. $fn = $conf['mediadir'] . '/' . utf8_encodeFN($id);
  413. } else {
  414. $ext = mimetype($id);
  415. $name = substr($id, 0, -1 * strlen($ext[0]) - 1);
  416. $fn = $conf['mediaolddir'] . '/' . utf8_encodeFN($name . '.' . ( (int) $rev ) . '.' . $ext[0]);
  417. }
  418. return $fn;
  419. }
  420. /**
  421. * Returns the full filepath to a localized file if local
  422. * version isn't found the english one is returned
  423. *
  424. * @param string $id The id of the local file
  425. * @param string $ext The file extension (usually txt)
  426. * @return string full filepath to localized file
  427. *
  428. * @author Andreas Gohr <andi@splitbrain.org>
  429. */
  430. function localeFN($id, $ext = 'txt')
  431. {
  432. global $conf;
  433. $file = DOKU_CONF . 'lang/' . $conf['lang'] . '/' . $id . '.' . $ext;
  434. if (!file_exists($file)) {
  435. $file = DOKU_INC . 'inc/lang/' . $conf['lang'] . '/' . $id . '.' . $ext;
  436. if (!file_exists($file)) {
  437. //fall back to english
  438. $file = DOKU_INC . 'inc/lang/en/' . $id . '.' . $ext;
  439. }
  440. }
  441. return $file;
  442. }
  443. /**
  444. * Resolve relative paths in IDs
  445. *
  446. * Do not call directly use resolve_mediaid or resolve_pageid
  447. * instead
  448. *
  449. * Partyly based on a cleanPath function found at
  450. * http://php.net/manual/en/function.realpath.php#57016
  451. *
  452. * @deprecated 2020-09-30
  453. * @param string $ns namespace which is context of id
  454. * @param string $id relative id
  455. * @param bool $clean flag indicating that id should be cleaned
  456. * @return string
  457. */
  458. function resolve_id($ns, $id, $clean = true)
  459. {
  460. global $conf;
  461. dbg_deprecated(\dokuwiki\File\Resolver::class . ' and its children');
  462. // some pre cleaning for useslash:
  463. if ($conf['useslash']) $id = str_replace('/', ':', $id);
  464. // if the id starts with a dot we need to handle the
  465. // relative stuff
  466. if ($id && $id[0] == '.') {
  467. // normalize initial dots without a colon
  468. $id = preg_replace('/^((\.+:)*)(\.+)(?=[^:\.])/', '\1\3:', $id);
  469. // prepend the current namespace
  470. $id = $ns . ':' . $id;
  471. // cleanup relatives
  472. $result = array();
  473. $pathA = explode(':', $id);
  474. if (!$pathA[0]) $result[] = '';
  475. foreach ($pathA as $key => $dir) {
  476. if ($dir == '..') {
  477. if (end($result) == '..') {
  478. $result[] = '..';
  479. } elseif (!array_pop($result)) {
  480. $result[] = '..';
  481. }
  482. } elseif ($dir && $dir != '.') {
  483. $result[] = $dir;
  484. }
  485. }
  486. if (!end($pathA)) $result[] = '';
  487. $id = implode(':', $result);
  488. } elseif ($ns !== false && strpos($id, ':') === false) {
  489. //if link contains no namespace. add current namespace (if any)
  490. $id = $ns . ':' . $id;
  491. }
  492. if ($clean) $id = cleanID($id);
  493. return $id;
  494. }
  495. /**
  496. * Returns a full media id
  497. *
  498. * @param string $ns namespace which is context of id
  499. * @param string &$media (reference) relative media id, updated to resolved id
  500. * @param bool &$exists (reference) updated with existance of media
  501. * @param int|string $rev
  502. * @param bool $date_at
  503. * @deprecated 2020-09-30
  504. */
  505. function resolve_mediaid($ns, &$media, &$exists, $rev = '', $date_at = false)
  506. {
  507. dbg_deprecated(MediaResolver::class);
  508. $resolver = new MediaResolver("$ns:deprecated");
  509. $media = $resolver->resolveId($media, $rev, $date_at);
  510. $exists = media_exists($media, $rev, false, $date_at);
  511. }
  512. /**
  513. * Returns a full page id
  514. *
  515. * @deprecated 2020-09-30
  516. * @param string $ns namespace which is context of id
  517. * @param string &$page (reference) relative page id, updated to resolved id
  518. * @param bool &$exists (reference) updated with existance of media
  519. * @param string $rev
  520. * @param bool $date_at
  521. */
  522. function resolve_pageid($ns, &$page, &$exists, $rev = '', $date_at = false)
  523. {
  524. dbg_deprecated(PageResolver::class);
  525. global $ID;
  526. if (getNS($ID) == $ns) {
  527. $context = $ID; // this is usually the case
  528. } else {
  529. $context = "$ns:deprecated"; // only used when a different context namespace was given
  530. }
  531. $resolver = new PageResolver($context);
  532. $page = $resolver->resolveId($page, $rev, $date_at);
  533. $exists = page_exists($page, $rev, false, $date_at);
  534. }
  535. /**
  536. * Returns the name of a cachefile from given data
  537. *
  538. * The needed directory is created by this function!
  539. *
  540. * @author Andreas Gohr <andi@splitbrain.org>
  541. *
  542. * @param string $data This data is used to create a unique md5 name
  543. * @param string $ext This is appended to the filename if given
  544. * @return string The filename of the cachefile
  545. */
  546. function getCacheName($data, $ext = '')
  547. {
  548. global $conf;
  549. $md5 = md5($data);
  550. $file = $conf['cachedir'] . '/' . $md5[0] . '/' . $md5 . $ext;
  551. io_makeFileDir($file);
  552. return $file;
  553. }
  554. /**
  555. * Checks a pageid against $conf['hidepages']
  556. *
  557. * @author Andreas Gohr <gohr@cosmocode.de>
  558. *
  559. * @param string $id page id
  560. * @return bool
  561. */
  562. function isHiddenPage($id)
  563. {
  564. $data = array(
  565. 'id' => $id,
  566. 'hidden' => false
  567. );
  568. \dokuwiki\Extension\Event::createAndTrigger('PAGEUTILS_ID_HIDEPAGE', $data, '_isHiddenPage');
  569. return $data['hidden'];
  570. }
  571. /**
  572. * callback checks if page is hidden
  573. *
  574. * @param array $data event data - see isHiddenPage()
  575. */
  576. function _isHiddenPage(&$data)
  577. {
  578. global $conf;
  579. global $ACT;
  580. if ($data['hidden']) return;
  581. if (empty($conf['hidepages'])) return;
  582. if ($ACT == 'admin') return;
  583. if (preg_match('/' . $conf['hidepages'] . '/ui', ':' . $data['id'])) {
  584. $data['hidden'] = true;
  585. }
  586. }
  587. /**
  588. * Reverse of isHiddenPage
  589. *
  590. * @author Andreas Gohr <gohr@cosmocode.de>
  591. *
  592. * @param string $id page id
  593. * @return bool
  594. */
  595. function isVisiblePage($id)
  596. {
  597. return !isHiddenPage($id);
  598. }
  599. /**
  600. * Format an id for output to a user
  601. *
  602. * Namespaces are denoted by a trailing “:*”. The root namespace is
  603. * “*”. Output is escaped.
  604. *
  605. * @author Adrian Lang <lang@cosmocode.de>
  606. *
  607. * @param string $id page id
  608. * @return string
  609. */
  610. function prettyprint_id($id)
  611. {
  612. if (!$id || $id === ':') {
  613. return '*';
  614. }
  615. if ((substr($id, -1, 1) === ':')) {
  616. $id .= '*';
  617. }
  618. return hsc($id);
  619. }
  620. /**
  621. * Encode a UTF-8 filename to use on any filesystem
  622. *
  623. * Uses the 'fnencode' option to determine encoding
  624. *
  625. * When the second parameter is true the string will
  626. * be encoded only if non ASCII characters are detected -
  627. * This makes it safe to run it multiple times on the
  628. * same string (default is true)
  629. *
  630. * @author Andreas Gohr <andi@splitbrain.org>
  631. * @see urlencode
  632. *
  633. * @param string $file file name
  634. * @param bool $safe if true, only encoded when non ASCII characters detected
  635. * @return string
  636. */
  637. function utf8_encodeFN($file, $safe = true)
  638. {
  639. global $conf;
  640. if ($conf['fnencode'] == 'utf-8') return $file;
  641. if ($safe && preg_match('#^[a-zA-Z0-9/_\-\.%]+$#', $file)) {
  642. return $file;
  643. }
  644. if ($conf['fnencode'] == 'safe') {
  645. return SafeFN::encode($file);
  646. }
  647. $file = urlencode($file);
  648. $file = str_replace('%2F', '/', $file);
  649. return $file;
  650. }
  651. /**
  652. * Decode a filename back to UTF-8
  653. *
  654. * Uses the 'fnencode' option to determine encoding
  655. *
  656. * @author Andreas Gohr <andi@splitbrain.org>
  657. * @see urldecode
  658. *
  659. * @param string $file file name
  660. * @return string
  661. */
  662. function utf8_decodeFN($file)
  663. {
  664. global $conf;
  665. if ($conf['fnencode'] == 'utf-8') return $file;
  666. if ($conf['fnencode'] == 'safe') {
  667. return SafeFN::decode($file);
  668. }
  669. return urldecode($file);
  670. }
  671. /**
  672. * Find a page in the current namespace (determined from $ID) or any
  673. * higher namespace that can be accessed by the current user,
  674. * this condition can be overriden by an optional parameter.
  675. *
  676. * Used for sidebars, but can be used other stuff as well
  677. *
  678. * @todo add event hook
  679. *
  680. * @param string $page the pagename you're looking for
  681. * @param bool $useacl only return pages readable by the current user, false to ignore ACLs
  682. * @return false|string the full page id of the found page, false if any
  683. */
  684. function page_findnearest($page, $useacl = true)
  685. {
  686. if ((string) $page === '') return false;
  687. global $ID;
  688. $ns = $ID;
  689. do {
  690. $ns = getNS($ns);
  691. $pageid = cleanID("$ns:$page");
  692. if (page_exists($pageid) && (!$useacl || auth_quickaclcheck($pageid) >= AUTH_READ)) {
  693. return $pageid;
  694. }
  695. } while ($ns !== false);
  696. return false;
  697. }