PageRenderTime 68ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/elgg/mod/dokuwiki/lib/dokuwiki/inc/common.php

https://bitbucket.org/rhizomatik/lorea_production/
PHP | 1549 lines | 932 code | 157 blank | 460 comment | 218 complexity | efb7991b6af8d7ee399a4b9161b80356 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /**
  3. * Common DokuWiki functions
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Andreas Gohr <andi@splitbrain.org>
  7. */
  8. if(!defined('DOKU_INC')) die('meh.');
  9. require_once(DOKU_INC.'inc/io.php');
  10. require_once(DOKU_INC.'inc/changelog.php');
  11. require_once(DOKU_INC.'inc/utf8.php');
  12. require_once(DOKU_INC.'inc/mail.php');
  13. require_once(DOKU_INC.'inc/parserutils.php');
  14. require_once(DOKU_INC.'inc/infoutils.php');
  15. /**
  16. * These constants are used with the recents function
  17. */
  18. define('RECENTS_SKIP_DELETED',2);
  19. define('RECENTS_SKIP_MINORS',4);
  20. define('RECENTS_SKIP_SUBSPACES',8);
  21. define('RECENTS_MEDIA_CHANGES',16);
  22. /**
  23. * Wrapper around htmlspecialchars()
  24. *
  25. * @author Andreas Gohr <andi@splitbrain.org>
  26. * @see htmlspecialchars()
  27. */
  28. function hsc($string){
  29. return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
  30. }
  31. /**
  32. * print a newline terminated string
  33. *
  34. * You can give an indention as optional parameter
  35. *
  36. * @author Andreas Gohr <andi@splitbrain.org>
  37. */
  38. function ptln($string,$indent=0){
  39. echo str_repeat(' ', $indent)."$string\n";
  40. }
  41. /**
  42. * strips control characters (<32) from the given string
  43. *
  44. * @author Andreas Gohr <andi@splitbrain.org>
  45. */
  46. function stripctl($string){
  47. return preg_replace('/[\x00-\x1F]+/s','',$string);
  48. }
  49. /**
  50. * Return a secret token to be used for CSRF attack prevention
  51. *
  52. * @author Andreas Gohr <andi@splitbrain.org>
  53. * @link http://en.wikipedia.org/wiki/Cross-site_request_forgery
  54. * @link http://christ1an.blogspot.com/2007/04/preventing-csrf-efficiently.html
  55. * @return string
  56. */
  57. function getSecurityToken(){
  58. return md5(auth_cookiesalt().session_id());
  59. }
  60. /**
  61. * Check the secret CSRF token
  62. */
  63. function checkSecurityToken($token=null){
  64. if(!$_SERVER['REMOTE_USER']) return true; // no logged in user, no need for a check
  65. if(is_null($token)) $token = $_REQUEST['sectok'];
  66. if(getSecurityToken() != $token){
  67. msg('Security Token did not match. Possible CSRF attack.',-1);
  68. return false;
  69. }
  70. return true;
  71. }
  72. /**
  73. * Print a hidden form field with a secret CSRF token
  74. *
  75. * @author Andreas Gohr <andi@splitbrain.org>
  76. */
  77. function formSecurityToken($print=true){
  78. $ret = '<div class="no"><input type="hidden" name="sectok" value="'.getSecurityToken().'" /></div>'."\n";
  79. if($print){
  80. echo $ret;
  81. }else{
  82. return $ret;
  83. }
  84. }
  85. /**
  86. * Return info about the current document as associative
  87. * array.
  88. *
  89. * @author Andreas Gohr <andi@splitbrain.org>
  90. */
  91. function pageinfo(){
  92. global $ID;
  93. global $REV;
  94. global $RANGE;
  95. global $USERINFO;
  96. global $conf;
  97. global $lang;
  98. // include ID & REV not redundant, as some parts of DokuWiki may temporarily change $ID, e.g. p_wiki_xhtml
  99. // FIXME ... perhaps it would be better to ensure the temporary changes weren't necessary
  100. $info['id'] = $ID;
  101. $info['rev'] = $REV;
  102. // set info about manager/admin status.
  103. $info['isadmin'] = false;
  104. $info['ismanager'] = false;
  105. if(isset($_SERVER['REMOTE_USER'])){
  106. $info['userinfo'] = $USERINFO;
  107. $info['perm'] = auth_quickaclcheck($ID);
  108. $info['subscribed'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],false);
  109. $info['subscribedns'] = is_subscribed($ID,$_SERVER['REMOTE_USER'],true);
  110. $info['client'] = $_SERVER['REMOTE_USER'];
  111. if($info['perm'] == AUTH_ADMIN){
  112. $info['isadmin'] = true;
  113. $info['ismanager'] = true;
  114. }elseif(auth_ismanager()){
  115. $info['ismanager'] = true;
  116. }
  117. // if some outside auth were used only REMOTE_USER is set
  118. if(!$info['userinfo']['name']){
  119. $info['userinfo']['name'] = $_SERVER['REMOTE_USER'];
  120. }
  121. }else{
  122. $info['perm'] = auth_aclcheck($ID,'',null);
  123. $info['subscribed'] = false;
  124. $info['client'] = clientIP(true);
  125. }
  126. //error_log("dokuwiki actpageinfo!!".$info['perm']);
  127. $info['namespace'] = getNS($ID);
  128. $info['locked'] = checklock($ID);
  129. $info['filepath'] = fullpath(wikiFN($ID));
  130. $info['exists'] = @file_exists($info['filepath']);
  131. if($REV){
  132. //check if current revision was meant
  133. if($info['exists'] && (@filemtime($info['filepath'])==$REV)){
  134. $REV = '';
  135. }elseif($RANGE){
  136. //section editing does not work with old revisions!
  137. $REV = '';
  138. $RANGE = '';
  139. msg($lang['nosecedit'],0);
  140. }else{
  141. //really use old revision
  142. $info['filepath'] = fullpath(wikiFN($ID,$REV));
  143. $info['exists'] = @file_exists($info['filepath']);
  144. }
  145. }
  146. $info['rev'] = $REV;
  147. if($info['exists']){
  148. $info['writable'] = (is_writable($info['filepath']) &&
  149. ($info['perm'] >= AUTH_EDIT));
  150. }else{
  151. $info['writable'] = ($info['perm'] >= AUTH_CREATE);
  152. }
  153. $info['editable'] = ($info['writable'] && empty($info['lock']));
  154. $info['lastmod'] = @filemtime($info['filepath']);
  155. //load page meta data
  156. $info['meta'] = p_get_metadata($ID);
  157. //who's the editor
  158. if($REV){
  159. $revinfo = getRevisionInfo($ID, $REV, 1024);
  160. }else{
  161. if (is_array($info['meta']['last_change'])) {
  162. $revinfo = $info['meta']['last_change'];
  163. } else {
  164. $revinfo = getRevisionInfo($ID, $info['lastmod'], 1024);
  165. // cache most recent changelog line in metadata if missing and still valid
  166. if ($revinfo!==false) {
  167. $info['meta']['last_change'] = $revinfo;
  168. p_set_metadata($ID, array('last_change' => $revinfo));
  169. }
  170. }
  171. }
  172. //and check for an external edit
  173. if($revinfo!==false && $revinfo['date']!=$info['lastmod']){
  174. // cached changelog line no longer valid
  175. $revinfo = false;
  176. $info['meta']['last_change'] = $revinfo;
  177. p_set_metadata($ID, array('last_change' => $revinfo));
  178. }
  179. $info['ip'] = $revinfo['ip'];
  180. $info['user'] = $revinfo['user'];
  181. $info['sum'] = $revinfo['sum'];
  182. // See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
  183. // Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
  184. if($revinfo['user']){
  185. $info['editor'] = $revinfo['user'];
  186. }else{
  187. $info['editor'] = $revinfo['ip'];
  188. }
  189. // draft
  190. $draft = getCacheName($info['client'].$ID,'.draft');
  191. if(@file_exists($draft)){
  192. if(@filemtime($draft) < @filemtime(wikiFN($ID))){
  193. // remove stale draft
  194. @unlink($draft);
  195. }else{
  196. $info['draft'] = $draft;
  197. }
  198. }
  199. // mobile detection
  200. $info['ismobile'] = clientismobile();
  201. return $info;
  202. }
  203. /**
  204. * Build an string of URL parameters
  205. *
  206. * @author Andreas Gohr
  207. */
  208. function buildURLparams($params, $sep='&amp;'){
  209. $url = '';
  210. $amp = false;
  211. foreach($params as $key => $val){
  212. if($amp) $url .= $sep;
  213. $url .= $key.'=';
  214. $url .= rawurlencode((string)$val);
  215. $amp = true;
  216. }
  217. return $url;
  218. }
  219. /**
  220. * Build an string of html tag attributes
  221. *
  222. * Skips keys starting with '_', values get HTML encoded
  223. *
  224. * @author Andreas Gohr
  225. */
  226. function buildAttributes($params,$skipempty=false){
  227. $url = '';
  228. foreach($params as $key => $val){
  229. if($key{0} == '_') continue;
  230. if($val === '' && $skipempty) continue;
  231. $url .= $key.'="';
  232. $url .= htmlspecialchars ($val);
  233. $url .= '" ';
  234. }
  235. return $url;
  236. }
  237. /**
  238. * This builds the breadcrumb trail and returns it as array
  239. *
  240. * @author Andreas Gohr <andi@splitbrain.org>
  241. */
  242. function breadcrumbs(){
  243. // we prepare the breadcrumbs early for quick session closing
  244. static $crumbs = null;
  245. if($crumbs != null) return $crumbs;
  246. global $ID;
  247. global $ACT;
  248. global $conf;
  249. //first visit?
  250. $crumbs = isset($_SESSION[DOKU_COOKIE]['bc']) ? $_SESSION[DOKU_COOKIE]['bc'] : array();
  251. //we only save on show and existing wiki documents
  252. $file = wikiFN($ID);
  253. if($ACT != 'show' || !@file_exists($file)){
  254. $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
  255. return $crumbs;
  256. }
  257. // page names
  258. $name = noNSorNS($ID);
  259. if (useHeading('navigation')) {
  260. // get page title
  261. $title = p_get_first_heading($ID,true);
  262. if ($title) {
  263. $name = $title;
  264. }
  265. }
  266. //remove ID from array
  267. if (isset($crumbs[$ID])) {
  268. unset($crumbs[$ID]);
  269. }
  270. //add to array
  271. $crumbs[$ID] = $name;
  272. //reduce size
  273. while(count($crumbs) > $conf['breadcrumbs']){
  274. array_shift($crumbs);
  275. }
  276. //save to session
  277. $_SESSION[DOKU_COOKIE]['bc'] = $crumbs;
  278. return $crumbs;
  279. }
  280. /**
  281. * Filter for page IDs
  282. *
  283. * This is run on a ID before it is outputted somewhere
  284. * currently used to replace the colon with something else
  285. * on Windows systems and to have proper URL encoding
  286. *
  287. * Urlencoding is ommitted when the second parameter is false
  288. *
  289. * @author Andreas Gohr <andi@splitbrain.org>
  290. */
  291. function idfilter($id,$ue=true){
  292. global $conf;
  293. if ($conf['useslash'] && $conf['userewrite']){
  294. $id = strtr($id,':','/');
  295. }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
  296. $conf['userewrite']) {
  297. $id = strtr($id,':',';');
  298. }
  299. if($ue){
  300. $id = rawurlencode($id);
  301. $id = str_replace('%3A',':',$id); //keep as colon
  302. $id = str_replace('%2F','/',$id); //keep as slash
  303. }
  304. return $id;
  305. }
  306. /**
  307. * This builds a link to a wikipage
  308. *
  309. * It handles URL rewriting and adds additional parameter if
  310. * given in $more
  311. *
  312. * @author Andreas Gohr <andi@splitbrain.org>
  313. */
  314. function wl($id='',$more='',$abs=false,$sep='&amp;'){
  315. global $conf;
  316. if(is_array($more)){
  317. $more = buildURLparams($more,$sep);
  318. }else{
  319. $more = str_replace(',',$sep,$more);
  320. }
  321. $id = idfilter($id);
  322. if($abs){
  323. $xlink = DOKU_URL;
  324. }else{
  325. $xlink = DOKU_BASE;
  326. }
  327. if($conf['userewrite'] == 2){
  328. $xlink .= DOKU_SCRIPT.'/'.$id;
  329. if($more) $xlink .= '?'.$more;
  330. }elseif($conf['userewrite']){
  331. $xlink .= $id;
  332. if($more) $xlink .= '?'.$more;
  333. }elseif($id){
  334. $xlink .= DOKU_SCRIPT.'?id='.$id;
  335. if($more) $xlink .= $sep.$more;
  336. }else{
  337. $xlink .= DOKU_SCRIPT;
  338. if($more) $xlink .= '?'.$more;
  339. }
  340. return $xlink;
  341. }
  342. /**
  343. * This builds a link to an alternate page format
  344. *
  345. * Handles URL rewriting if enabled. Follows the style of wl().
  346. *
  347. * @author Ben Coburn <btcoburn@silicodon.net>
  348. */
  349. function exportlink($id='',$format='raw',$more='',$abs=false,$sep='&amp;'){
  350. global $conf;
  351. if(is_array($more)){
  352. $more = buildURLparams($more,$sep);
  353. }else{
  354. $more = str_replace(',',$sep,$more);
  355. }
  356. $format = rawurlencode($format);
  357. $id = idfilter($id);
  358. if($abs){
  359. $xlink = DOKU_URL;
  360. }else{
  361. $xlink = DOKU_BASE;
  362. }
  363. if($conf['userewrite'] == 2){
  364. $xlink .= DOKU_SCRIPT.'/'.$id.'?do=export_'.$format;
  365. if($more) $xlink .= $sep.$more;
  366. }elseif($conf['userewrite'] == 1){
  367. $xlink .= '_export/'.$format.'/'.$id;
  368. if($more) $xlink .= '?'.$more;
  369. }else{
  370. $xlink .= DOKU_SCRIPT.'?do=export_'.$format.$sep.'id='.$id;
  371. if($more) $xlink .= $sep.$more;
  372. }
  373. return $xlink;
  374. }
  375. /**
  376. * Build a link to a media file
  377. *
  378. * Will return a link to the detail page if $direct is false
  379. *
  380. * The $more parameter should always be given as array, the function then
  381. * will strip default parameters to produce even cleaner URLs
  382. *
  383. * @param string $id - the media file id or URL
  384. * @param mixed $more - string or array with additional parameters
  385. * @param boolean $direct - link to detail page if false
  386. * @param string $sep - URL parameter separator
  387. * @param boolean $abs - Create an absolute URL
  388. */
  389. function ml($id='',$more='',$direct=true,$sep='&amp;',$abs=false){
  390. global $conf;
  391. if(is_array($more)){
  392. // strip defaults for shorter URLs
  393. if(isset($more['cache']) && $more['cache'] == 'cache') unset($more['cache']);
  394. if(!$more['w']) unset($more['w']);
  395. if(!$more['h']) unset($more['h']);
  396. if(isset($more['id']) && $direct) unset($more['id']);
  397. $more = buildURLparams($more,$sep);
  398. }else{
  399. $more = str_replace('cache=cache','',$more); //skip default
  400. $more = str_replace(',,',',',$more);
  401. $more = str_replace(',',$sep,$more);
  402. }
  403. if($abs){
  404. $xlink = DOKU_URL;
  405. }else{
  406. $xlink = DOKU_BASE;
  407. }
  408. // external URLs are always direct without rewriting
  409. if(preg_match('#^(https?|ftp)://#i',$id)){
  410. $xlink .= 'lib/exe/fetch.php';
  411. // add hash:
  412. $xlink .= '?hash='.substr(md5(auth_cookiesalt().$id),0,6);
  413. if($more){
  414. $xlink .= $sep.$more;
  415. $xlink .= $sep.'media='.rawurlencode($id);
  416. }else{
  417. $xlink .= $sep.'media='.rawurlencode($id);
  418. }
  419. return $xlink;
  420. }
  421. $id = idfilter($id);
  422. // decide on scriptname
  423. if($direct){
  424. if($conf['userewrite'] == 1){
  425. $script = '_media';
  426. }else{
  427. $script = 'lib/exe/fetch.php';
  428. }
  429. }else{
  430. if($conf['userewrite'] == 1){
  431. $script = '_detail';
  432. }else{
  433. $script = 'lib/exe/detail.php';
  434. }
  435. }
  436. // build URL based on rewrite mode
  437. if($conf['userewrite']){
  438. $xlink .= $script.'/'.$id;
  439. if($more) $xlink .= '?'.$more;
  440. }else{
  441. if($more){
  442. $xlink .= $script.'?'.$more;
  443. $xlink .= $sep.'media='.$id;
  444. }else{
  445. $xlink .= $script.'?media='.$id;
  446. }
  447. }
  448. return $xlink;
  449. }
  450. /**
  451. * Just builds a link to a script
  452. *
  453. * @todo maybe obsolete
  454. * @author Andreas Gohr <andi@splitbrain.org>
  455. */
  456. function script($script='doku.php'){
  457. # $link = getBaseURL();
  458. # $link .= $script;
  459. # return $link;
  460. return DOKU_BASE.DOKU_SCRIPT;
  461. }
  462. /**
  463. * Spamcheck against wordlist
  464. *
  465. * Checks the wikitext against a list of blocked expressions
  466. * returns true if the text contains any bad words
  467. *
  468. * Triggers COMMON_WORDBLOCK_BLOCKED
  469. *
  470. * Action Plugins can use this event to inspect the blocked data
  471. * and gain information about the user who was blocked.
  472. *
  473. * Event data:
  474. * data['matches'] - array of matches
  475. * data['userinfo'] - information about the blocked user
  476. * [ip] - ip address
  477. * [user] - username (if logged in)
  478. * [mail] - mail address (if logged in)
  479. * [name] - real name (if logged in)
  480. *
  481. * @author Andreas Gohr <andi@splitbrain.org>
  482. * @author Michael Klier <chi@chimeric.de>
  483. * @param string $text - optional text to check, if not given the globals are used
  484. * @return bool - true if a spam word was found
  485. */
  486. function checkwordblock($text=''){
  487. global $TEXT;
  488. global $PRE;
  489. global $SUF;
  490. global $conf;
  491. global $INFO;
  492. if(!$conf['usewordblock']) return false;
  493. if(!$text) $text = "$PRE $TEXT $SUF";
  494. // we prepare the text a tiny bit to prevent spammers circumventing URL checks
  495. $text = preg_replace('!(\b)(www\.[\w.:?\-;,]+?\.[\w.:?\-;,]+?[\w/\#~:.?+=&%@\!\-.:?\-;,]+?)([.:?\-;,]*[^\w/\#~:.?+=&%@\!\-.:?\-;,])!i','\1http://\2 \2\3',$text);
  496. $wordblocks = getWordblocks();
  497. //how many lines to read at once (to work around some PCRE limits)
  498. if(version_compare(phpversion(),'4.3.0','<')){
  499. //old versions of PCRE define a maximum of parenthesises even if no
  500. //backreferences are used - the maximum is 99
  501. //this is very bad performancewise and may even be too high still
  502. $chunksize = 40;
  503. }else{
  504. //read file in chunks of 200 - this should work around the
  505. //MAX_PATTERN_SIZE in modern PCRE
  506. $chunksize = 200;
  507. }
  508. while($blocks = array_splice($wordblocks,0,$chunksize)){
  509. $re = array();
  510. #build regexp from blocks
  511. foreach($blocks as $block){
  512. $block = preg_replace('/#.*$/','',$block);
  513. $block = trim($block);
  514. if(empty($block)) continue;
  515. $re[] = $block;
  516. }
  517. if(count($re) && preg_match('#('.join('|',$re).')#si',$text,$matches)) {
  518. //prepare event data
  519. $data['matches'] = $matches;
  520. $data['userinfo']['ip'] = $_SERVER['REMOTE_ADDR'];
  521. if($_SERVER['REMOTE_USER']) {
  522. $data['userinfo']['user'] = $_SERVER['REMOTE_USER'];
  523. $data['userinfo']['name'] = $INFO['userinfo']['name'];
  524. $data['userinfo']['mail'] = $INFO['userinfo']['mail'];
  525. }
  526. $callback = create_function('', 'return true;');
  527. return trigger_event('COMMON_WORDBLOCK_BLOCKED', $data, $callback, true);
  528. }
  529. }
  530. return false;
  531. }
  532. /**
  533. * Return the IP of the client
  534. *
  535. * Honours X-Forwarded-For and X-Real-IP Proxy Headers
  536. *
  537. * It returns a comma separated list of IPs if the above mentioned
  538. * headers are set. If the single parameter is set, it tries to return
  539. * a routable public address, prefering the ones suplied in the X
  540. * headers
  541. *
  542. * @param boolean $single If set only a single IP is returned
  543. * @author Andreas Gohr <andi@splitbrain.org>
  544. */
  545. function clientIP($single=false){
  546. $ip = array();
  547. $ip[] = $_SERVER['REMOTE_ADDR'];
  548. if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
  549. $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']));
  550. if(!empty($_SERVER['HTTP_X_REAL_IP']))
  551. $ip = array_merge($ip,explode(',',$_SERVER['HTTP_X_REAL_IP']));
  552. // some IPv4/v6 regexps borrowed from Feyd
  553. // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479
  554. $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])';
  555. $hex_digit = '[A-Fa-f0-9]';
  556. $h16 = "{$hex_digit}{1,4}";
  557. $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet";
  558. $ls32 = "(?:$h16:$h16|$IPv4Address)";
  559. $IPv6Address =
  560. "(?:(?:{$IPv4Address})|(?:".
  561. "(?:$h16:){6}$ls32" .
  562. "|::(?:$h16:){5}$ls32" .
  563. "|(?:$h16)?::(?:$h16:){4}$ls32" .
  564. "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" .
  565. "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" .
  566. "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" .
  567. "|(?:(?:$h16:){0,4}$h16)?::$ls32" .
  568. "|(?:(?:$h16:){0,5}$h16)?::$h16" .
  569. "|(?:(?:$h16:){0,6}$h16)?::" .
  570. ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)";
  571. // remove any non-IP stuff
  572. $cnt = count($ip);
  573. $match = array();
  574. for($i=0; $i<$cnt; $i++){
  575. if(preg_match("/^$IPv4Address$/",$ip[$i],$match) || preg_match("/^$IPv6Address$/",$ip[$i],$match)) {
  576. $ip[$i] = $match[0];
  577. } else {
  578. $ip[$i] = '';
  579. }
  580. if(empty($ip[$i])) unset($ip[$i]);
  581. }
  582. $ip = array_values(array_unique($ip));
  583. if(!$ip[0]) $ip[0] = '0.0.0.0'; // for some strange reason we don't have a IP
  584. if(!$single) return join(',',$ip);
  585. // decide which IP to use, trying to avoid local addresses
  586. $ip = array_reverse($ip);
  587. foreach($ip as $i){
  588. if(preg_match('/^(127\.|10\.|192\.168\.|172\.((1[6-9])|(2[0-9])|(3[0-1]))\.)/',$i)){
  589. continue;
  590. }else{
  591. return $i;
  592. }
  593. }
  594. // still here? just use the first (last) address
  595. return $ip[0];
  596. }
  597. /**
  598. * Check if the browser is on a mobile device
  599. *
  600. * Adapted from the example code at url below
  601. *
  602. * @link http://www.brainhandles.com/2007/10/15/detecting-mobile-browsers/#code
  603. */
  604. function clientismobile(){
  605. if(isset($_SERVER['HTTP_X_WAP_PROFILE'])) return true;
  606. if(preg_match('/wap\.|\.wap/i',$_SERVER['HTTP_ACCEPT'])) return true;
  607. if(!isset($_SERVER['HTTP_USER_AGENT'])) return false;
  608. $uamatches = 'midp|j2me|avantg|docomo|novarra|palmos|palmsource|240x320|opwv|chtml|pda|windows ce|mmp\/|blackberry|mib\/|symbian|wireless|nokia|hand|mobi|phone|cdm|up\.b|audio|SIE\-|SEC\-|samsung|HTC|mot\-|mitsu|sagem|sony|alcatel|lg|erics|vx|NEC|philips|mmm|xx|panasonic|sharp|wap|sch|rover|pocket|benq|java|pt|pg|vox|amoi|bird|compal|kg|voda|sany|kdd|dbt|sendo|sgh|gradi|jb|\d\d\di|moto';
  609. if(preg_match("/$uamatches/i",$_SERVER['HTTP_USER_AGENT'])) return true;
  610. return false;
  611. }
  612. /**
  613. * Convert one or more comma separated IPs to hostnames
  614. *
  615. * @author Glen Harris <astfgl@iamnota.org>
  616. * @returns a comma separated list of hostnames
  617. */
  618. function gethostsbyaddrs($ips){
  619. $hosts = array();
  620. $ips = explode(',',$ips);
  621. if(is_array($ips)) {
  622. foreach($ips as $ip){
  623. $hosts[] = gethostbyaddr(trim($ip));
  624. }
  625. return join(',',$hosts);
  626. } else {
  627. return gethostbyaddr(trim($ips));
  628. }
  629. }
  630. /**
  631. * Checks if a given page is currently locked.
  632. *
  633. * removes stale lockfiles
  634. *
  635. * @author Andreas Gohr <andi@splitbrain.org>
  636. */
  637. function checklock($id){
  638. global $conf;
  639. $lock = wikiLockFN($id);
  640. //no lockfile
  641. if(!@file_exists($lock)) return false;
  642. //lockfile expired
  643. if((time() - filemtime($lock)) > $conf['locktime']){
  644. @unlink($lock);
  645. return false;
  646. }
  647. //my own lock
  648. $ip = io_readFile($lock);
  649. if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
  650. return false;
  651. }
  652. return $ip;
  653. }
  654. /**
  655. * Lock a page for editing
  656. *
  657. * @author Andreas Gohr <andi@splitbrain.org>
  658. */
  659. function lock($id){
  660. $lock = wikiLockFN($id);
  661. if($_SERVER['REMOTE_USER']){
  662. io_saveFile($lock,$_SERVER['REMOTE_USER']);
  663. }else{
  664. io_saveFile($lock,clientIP());
  665. }
  666. }
  667. /**
  668. * Unlock a page if it was locked by the user
  669. *
  670. * @author Andreas Gohr <andi@splitbrain.org>
  671. * @return bool true if a lock was removed
  672. */
  673. function unlock($id){
  674. $lock = wikiLockFN($id);
  675. if(@file_exists($lock)){
  676. $ip = io_readFile($lock);
  677. if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
  678. @unlink($lock);
  679. return true;
  680. }
  681. }
  682. return false;
  683. }
  684. /**
  685. * convert line ending to unix format
  686. *
  687. * @see formText() for 2crlf conversion
  688. * @author Andreas Gohr <andi@splitbrain.org>
  689. */
  690. function cleanText($text){
  691. $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
  692. return $text;
  693. }
  694. /**
  695. * Prepares text for print in Webforms by encoding special chars.
  696. * It also converts line endings to Windows format which is
  697. * pseudo standard for webforms.
  698. *
  699. * @see cleanText() for 2unix conversion
  700. * @author Andreas Gohr <andi@splitbrain.org>
  701. */
  702. function formText($text){
  703. $text = str_replace("\012","\015\012",$text);
  704. return htmlspecialchars($text);
  705. }
  706. /**
  707. * Returns the specified local text in raw format
  708. *
  709. * @author Andreas Gohr <andi@splitbrain.org>
  710. */
  711. function rawLocale($id){
  712. return io_readFile(localeFN($id));
  713. }
  714. /**
  715. * Returns the raw WikiText
  716. *
  717. * @author Andreas Gohr <andi@splitbrain.org>
  718. */
  719. function rawWiki($id,$rev=''){
  720. return io_readWikiPage(wikiFN($id, $rev), $id, $rev);
  721. }
  722. /**
  723. * Returns the pagetemplate contents for the ID's namespace
  724. *
  725. * @author Andreas Gohr <andi@splitbrain.org>
  726. */
  727. function pageTemplate($data){
  728. $id = $data[0];
  729. global $conf;
  730. global $INFO;
  731. $path = dirname(wikiFN($id));
  732. if(@file_exists($path.'/_template.txt')){
  733. $tpl = io_readFile($path.'/_template.txt');
  734. }else{
  735. // search upper namespaces for templates
  736. $len = strlen(rtrim($conf['datadir'],'/'));
  737. while (strlen($path) >= $len){
  738. if(@file_exists($path.'/__template.txt')){
  739. $tpl = io_readFile($path.'/__template.txt');
  740. break;
  741. }
  742. $path = substr($path, 0, strrpos($path, '/'));
  743. }
  744. }
  745. if(!$tpl) return '';
  746. // replace placeholders
  747. $file = noNS($id);
  748. $page = strtr($file,'_',' ');
  749. $tpl = str_replace(array(
  750. '@ID@',
  751. '@NS@',
  752. '@FILE@',
  753. '@!FILE@',
  754. '@!FILE!@',
  755. '@PAGE@',
  756. '@!PAGE@',
  757. '@!!PAGE@',
  758. '@!PAGE!@',
  759. '@USER@',
  760. '@NAME@',
  761. '@MAIL@',
  762. '@DATE@',
  763. ),
  764. array(
  765. $id,
  766. getNS($id),
  767. $file,
  768. utf8_ucfirst($file),
  769. utf8_strtoupper($file),
  770. $page,
  771. utf8_ucfirst($page),
  772. utf8_ucwords($page),
  773. utf8_strtoupper($page),
  774. $_SERVER['REMOTE_USER'],
  775. $INFO['userinfo']['name'],
  776. $INFO['userinfo']['mail'],
  777. $conf['dformat'],
  778. ), $tpl);
  779. // we need the callback to work around strftime's char limit
  780. $tpl = preg_replace_callback('/%./',create_function('$m','return strftime($m[0]);'),$tpl);
  781. return $tpl;
  782. }
  783. /**
  784. * Returns the raw Wiki Text in three slices.
  785. *
  786. * The range parameter needs to have the form "from-to"
  787. * and gives the range of the section in bytes - no
  788. * UTF-8 awareness is needed.
  789. * The returned order is prefix, section and suffix.
  790. *
  791. * @author Andreas Gohr <andi@splitbrain.org>
  792. */
  793. function rawWikiSlices($range,$id,$rev=''){
  794. list($from,$to) = explode('-',$range,2);
  795. $text = io_readWikiPage(wikiFN($id, $rev), $id, $rev);
  796. if(!$from) $from = 0;
  797. if(!$to) $to = strlen($text)+1;
  798. $slices[0] = substr($text,0,$from-1);
  799. $slices[1] = substr($text,$from-1,$to-$from);
  800. $slices[2] = substr($text,$to);
  801. return $slices;
  802. }
  803. /**
  804. * Joins wiki text slices
  805. *
  806. * function to join the text slices with correct lineendings again.
  807. * When the pretty parameter is set to true it adds additional empty
  808. * lines between sections if needed (used on saving).
  809. *
  810. * @author Andreas Gohr <andi@splitbrain.org>
  811. */
  812. function con($pre,$text,$suf,$pretty=false){
  813. if($pretty){
  814. if($pre && substr($pre,-1) != "\n") $pre .= "\n";
  815. if($suf && substr($text,-1) != "\n") $text .= "\n";
  816. }
  817. // Avoid double newline above section when saving section edit
  818. //if($pre) $pre .= "\n";
  819. if($suf) $text .= "\n";
  820. return $pre.$text.$suf;
  821. }
  822. /**
  823. * Saves a wikitext by calling io_writeWikiPage.
  824. * Also directs changelog and attic updates.
  825. *
  826. * @author Andreas Gohr <andi@splitbrain.org>
  827. * @author Ben Coburn <btcoburn@silicodon.net>
  828. */
  829. function saveWikiText($id,$text,$summary,$minor=false){
  830. /* Note to developers:
  831. This code is subtle and delicate. Test the behavior of
  832. the attic and changelog with dokuwiki and external edits
  833. after any changes. External edits change the wiki page
  834. directly without using php or dokuwiki.
  835. */
  836. global $conf;
  837. global $lang;
  838. global $REV;
  839. // ignore if no changes were made
  840. if($text == rawWiki($id,'')){
  841. return;
  842. }
  843. $file = wikiFN($id);
  844. $old = @filemtime($file); // from page
  845. $wasRemoved = empty($text);
  846. $wasCreated = !@file_exists($file);
  847. $wasReverted = ($REV==true);
  848. $newRev = false;
  849. $oldRev = getRevisions($id, -1, 1, 1024); // from changelog
  850. $oldRev = (int)(empty($oldRev)?0:$oldRev[0]);
  851. if(!@file_exists(wikiFN($id, $old)) && @file_exists($file) && $old>=$oldRev) {
  852. // add old revision to the attic if missing
  853. saveOldRevision($id);
  854. // add a changelog entry if this edit came from outside dokuwiki
  855. if ($old>$oldRev) {
  856. addLogEntry($old, $id, DOKU_CHANGE_TYPE_EDIT, $lang['external_edit'], '', array('ExternalEdit'=>true));
  857. // remove soon to be stale instructions
  858. $cache = new cache_instructions($id, $file);
  859. $cache->removeCache();
  860. }
  861. }
  862. if ($wasRemoved){
  863. // Send "update" event with empty data, so plugins can react to page deletion
  864. $data = array(array($file, '', false), getNS($id), noNS($id), false);
  865. trigger_event('IO_WIKIPAGE_WRITE', $data);
  866. // pre-save deleted revision
  867. @touch($file);
  868. clearstatcache();
  869. $newRev = saveOldRevision($id);
  870. // remove empty file
  871. @unlink($file);
  872. // remove old meta info...
  873. $mfiles = metaFiles($id);
  874. $changelog = metaFN($id, '.changes');
  875. $metadata = metaFN($id, '.meta');
  876. foreach ($mfiles as $mfile) {
  877. // but keep per-page changelog to preserve page history and keep meta data
  878. if (@file_exists($mfile) && $mfile!==$changelog && $mfile!==$metadata) { @unlink($mfile); }
  879. }
  880. // purge meta data
  881. p_purge_metadata($id);
  882. $del = true;
  883. // autoset summary on deletion
  884. if(empty($summary)) $summary = $lang['deleted'];
  885. // remove empty namespaces
  886. io_sweepNS($id, 'datadir');
  887. io_sweepNS($id, 'mediadir');
  888. }else{
  889. // save file (namespace dir is created in io_writeWikiPage)
  890. io_writeWikiPage($file, $text, $id);
  891. // pre-save the revision, to keep the attic in sync
  892. $newRev = saveOldRevision($id);
  893. $del = false;
  894. }
  895. // select changelog line type
  896. $extra = '';
  897. $type = DOKU_CHANGE_TYPE_EDIT;
  898. if ($wasReverted) {
  899. $type = DOKU_CHANGE_TYPE_REVERT;
  900. $extra = $REV;
  901. }
  902. else if ($wasCreated) { $type = DOKU_CHANGE_TYPE_CREATE; }
  903. else if ($wasRemoved) { $type = DOKU_CHANGE_TYPE_DELETE; }
  904. else if ($minor && $conf['useacl'] && $_SERVER['REMOTE_USER']) { $type = DOKU_CHANGE_TYPE_MINOR_EDIT; } //minor edits only for logged in users
  905. addLogEntry($newRev, $id, $type, $summary, $extra);
  906. // send notify mails
  907. notify($id,'admin',$old,$summary,$minor);
  908. notify($id,'subscribers',$old,$summary,$minor);
  909. // update the purgefile (timestamp of the last time anything within the wiki was changed)
  910. io_saveFile($conf['cachedir'].'/purgefile',time());
  911. // if useheading is enabled, purge the cache of all linking pages
  912. if(useHeading('content')){
  913. require_once(DOKU_INC.'inc/fulltext.php');
  914. $pages = ft_backlinks($id);
  915. foreach ($pages as $page) {
  916. $cache = new cache_renderer($page, wikiFN($page), 'xhtml');
  917. $cache->removeCache();
  918. }
  919. }
  920. }
  921. /**
  922. * moves the current version to the attic and returns its
  923. * revision date
  924. *
  925. * @author Andreas Gohr <andi@splitbrain.org>
  926. */
  927. function saveOldRevision($id){
  928. global $conf;
  929. $oldf = wikiFN($id);
  930. if(!@file_exists($oldf)) return '';
  931. $date = filemtime($oldf);
  932. $newf = wikiFN($id,$date);
  933. io_writeWikiPage($newf, rawWiki($id), $id, $date);
  934. return $date;
  935. }
  936. /**
  937. * Sends a notify mail on page change or registration
  938. *
  939. * @param string $id The changed page
  940. * @param string $who Who to notify (admin|subscribers|register)
  941. * @param int $rev Old page revision
  942. * @param string $summary What changed
  943. * @param boolean $minor Is this a minor edit?
  944. * @param array $replace Additional string substitutions, @KEY@ to be replaced by value
  945. *
  946. * @author Andreas Gohr <andi@splitbrain.org>
  947. */
  948. function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
  949. global $lang;
  950. global $conf;
  951. global $INFO;
  952. // decide if there is something to do
  953. if($who == 'admin'){
  954. if(empty($conf['notify'])) return; //notify enabled?
  955. $text = rawLocale('mailtext');
  956. $to = $conf['notify'];
  957. $bcc = '';
  958. }elseif($who == 'subscribers'){
  959. if(!$conf['subscribers']) return; //subscribers enabled?
  960. if($conf['useacl'] && $_SERVER['REMOTE_USER'] && $minor) return; //skip minors
  961. $bcc = subscriber_addresslist($id,false);
  962. if(empty($bcc)) return;
  963. $to = '';
  964. $text = rawLocale('subscribermail');
  965. }elseif($who == 'register'){
  966. if(empty($conf['registernotify'])) return;
  967. $text = rawLocale('registermail');
  968. $to = $conf['registernotify'];
  969. $bcc = '';
  970. }else{
  971. return; //just to be safe
  972. }
  973. $ip = clientIP();
  974. $text = str_replace('@DATE@',dformat(),$text);
  975. $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
  976. $text = str_replace('@IPADDRESS@',$ip,$text);
  977. $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
  978. $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text);
  979. $text = str_replace('@PAGE@',$id,$text);
  980. $text = str_replace('@TITLE@',$conf['title'],$text);
  981. $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
  982. $text = str_replace('@SUMMARY@',$summary,$text);
  983. $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
  984. foreach ($replace as $key => $substitution) {
  985. $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
  986. }
  987. if($who == 'register'){
  988. $subject = $lang['mail_new_user'].' '.$summary;
  989. }elseif($rev){
  990. $subject = $lang['mail_changed'].' '.$id;
  991. $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
  992. require_once(DOKU_INC.'inc/DifferenceEngine.php');
  993. $df = new Diff(explode("\n",rawWiki($id,$rev)),
  994. explode("\n",rawWiki($id)));
  995. $dformat = new UnifiedDiffFormatter();
  996. $diff = $dformat->format($df);
  997. }else{
  998. $subject=$lang['mail_newpage'].' '.$id;
  999. $text = str_replace('@OLDPAGE@','none',$text);
  1000. $diff = rawWiki($id);
  1001. }
  1002. $text = str_replace('@DIFF@',$diff,$text);
  1003. $subject = '['.$conf['title'].'] '.$subject;
  1004. $from = $conf['mailfrom'];
  1005. $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from);
  1006. $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from);
  1007. $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from);
  1008. mail_send($to,$subject,$text,$from,'',$bcc);
  1009. }
  1010. /**
  1011. * extracts the query from a search engine referrer
  1012. *
  1013. * @author Andreas Gohr <andi@splitbrain.org>
  1014. * @author Todd Augsburger <todd@rollerorgans.com>
  1015. */
  1016. function getGoogleQuery(){
  1017. if (!isset($_SERVER['HTTP_REFERER'])) {
  1018. return '';
  1019. }
  1020. $url = parse_url($_SERVER['HTTP_REFERER']);
  1021. $query = array();
  1022. // temporary workaround against PHP bug #49733
  1023. // see http://bugs.php.net/bug.php?id=49733
  1024. if(UTF8_MBSTRING) $enc = mb_internal_encoding();
  1025. parse_str($url['query'],$query);
  1026. if(UTF8_MBSTRING) mb_internal_encoding($enc);
  1027. $q = '';
  1028. if(isset($query['q']))
  1029. $q = $query['q']; // google, live/msn, aol, ask, altavista, alltheweb, gigablast
  1030. elseif(isset($query['p']))
  1031. $q = $query['p']; // yahoo
  1032. elseif(isset($query['query']))
  1033. $q = $query['query']; // lycos, netscape, clusty, hotbot
  1034. elseif(preg_match("#a9\.com#i",$url['host'])) // a9
  1035. $q = urldecode(ltrim($url['path'],'/'));
  1036. if($q === '') return '';
  1037. $q = preg_split('/[\s\'"\\\\`()\]\[?:!\.{};,#+*<>\\/]+/',$q,-1,PREG_SPLIT_NO_EMPTY);
  1038. return $q;
  1039. }
  1040. /**
  1041. * Try to set correct locale
  1042. *
  1043. * @deprecated No longer used
  1044. * @author Andreas Gohr <andi@splitbrain.org>
  1045. */
  1046. function setCorrectLocale(){
  1047. global $conf;
  1048. global $lang;
  1049. $enc = strtoupper($lang['encoding']);
  1050. foreach ($lang['locales'] as $loc){
  1051. //try locale
  1052. if(@setlocale(LC_ALL,$loc)) return;
  1053. //try loceale with encoding
  1054. if(@setlocale(LC_ALL,"$loc.$enc")) return;
  1055. }
  1056. //still here? try to set from environment
  1057. @setlocale(LC_ALL,"");
  1058. }
  1059. /**
  1060. * Return the human readable size of a file
  1061. *
  1062. * @param int $size A file size
  1063. * @param int $dec A number of decimal places
  1064. * @author Martin Benjamin <b.martin@cybernet.ch>
  1065. * @author Aidan Lister <aidan@php.net>
  1066. * @version 1.0.0
  1067. */
  1068. function filesize_h($size, $dec = 1){
  1069. $sizes = array('B', 'KB', 'MB', 'GB');
  1070. $count = count($sizes);
  1071. $i = 0;
  1072. while ($size >= 1024 && ($i < $count - 1)) {
  1073. $size /= 1024;
  1074. $i++;
  1075. }
  1076. return round($size, $dec) . ' ' . $sizes[$i];
  1077. }
  1078. /**
  1079. * Return the given timestamp as human readable, fuzzy age
  1080. *
  1081. * @author Andreas Gohr <gohr@cosmocode.de>
  1082. */
  1083. function datetime_h($dt){
  1084. global $lang;
  1085. $ago = time() - $dt;
  1086. if($ago > 24*60*60*30*12*2){
  1087. return sprintf($lang['years'], round($ago/(24*60*60*30*12)));
  1088. }
  1089. if($ago > 24*60*60*30*2){
  1090. return sprintf($lang['months'], round($ago/(24*60*60*30)));
  1091. }
  1092. if($ago > 24*60*60*7*2){
  1093. return sprintf($lang['weeks'], round($ago/(24*60*60*7)));
  1094. }
  1095. if($ago > 24*60*60*2){
  1096. return sprintf($lang['days'], round($ago/(24*60*60)));
  1097. }
  1098. if($ago > 60*60*2){
  1099. return sprintf($lang['hours'], round($ago/(60*60)));
  1100. }
  1101. if($ago > 60*2){
  1102. return sprintf($lang['minutes'], round($ago/(60)));
  1103. }
  1104. return sprintf($lang['seconds'], $ago);
  1105. }
  1106. /**
  1107. * Wraps around strftime but provides support for fuzzy dates
  1108. *
  1109. * The format default to $conf['dformat']. It is passed to
  1110. * strftime - %f can be used to get the value from datetime_h()
  1111. *
  1112. * @see datetime_h
  1113. * @author Andreas Gohr <gohr@cosmocode.de>
  1114. */
  1115. function dformat($dt=null,$format=''){
  1116. global $conf;
  1117. if(is_null($dt)) $dt = time();
  1118. $dt = (int) $dt;
  1119. if(!$format) $format = $conf['dformat'];
  1120. $format = str_replace('%f',datetime_h($dt),$format);
  1121. return strftime($format,$dt);
  1122. }
  1123. /**
  1124. * return an obfuscated email address in line with $conf['mailguard'] setting
  1125. *
  1126. * @author Harry Fuecks <hfuecks@gmail.com>
  1127. * @author Christopher Smith <chris@jalakai.co.uk>
  1128. */
  1129. function obfuscate($email) {
  1130. global $conf;
  1131. switch ($conf['mailguard']) {
  1132. case 'visible' :
  1133. $obfuscate = array('@' => ' [at] ', '.' => ' [dot] ', '-' => ' [dash] ');
  1134. return strtr($email, $obfuscate);
  1135. case 'hex' :
  1136. $encode = '';
  1137. for ($x=0; $x < strlen($email); $x++) $encode .= '&#x' . bin2hex($email{$x}).';';
  1138. return $encode;
  1139. case 'none' :
  1140. default :
  1141. return $email;
  1142. }
  1143. }
  1144. /**
  1145. * Let us know if a user is tracking a page or a namespace
  1146. *
  1147. * @author Andreas Gohr <andi@splitbrain.org>
  1148. */
  1149. function is_subscribed($id,$uid,$ns=false){
  1150. if(!$ns) {
  1151. $file=metaFN($id,'.mlist');
  1152. } else {
  1153. if(!getNS($id)) {
  1154. $file = metaFN(getNS($id),'.mlist');
  1155. } else {
  1156. $file = metaFN(getNS($id),'/.mlist');
  1157. }
  1158. }
  1159. if (@file_exists($file)) {
  1160. $mlist = file($file);
  1161. $pos = array_search($uid."\n",$mlist);
  1162. return is_int($pos);
  1163. }
  1164. return false;
  1165. }
  1166. /**
  1167. * Return a string with the email addresses of all the
  1168. * users subscribed to a page
  1169. *
  1170. * @author Steven Danz <steven-danz@kc.rr.com>
  1171. */
  1172. function subscriber_addresslist($id,$self=true){
  1173. global $conf;
  1174. global $auth;
  1175. if (!$conf['subscribers']) return '';
  1176. $users = array();
  1177. $emails = array();
  1178. // load the page mlist file content
  1179. $mlist = array();
  1180. $file=metaFN($id,'.mlist');
  1181. if (@file_exists($file)) {
  1182. $mlist = file($file);
  1183. foreach ($mlist as $who) {
  1184. $who = rtrim($who);
  1185. if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
  1186. $users[$who] = true;
  1187. }
  1188. }
  1189. // load also the namespace mlist file content
  1190. $ns = getNS($id);
  1191. while ($ns) {
  1192. $nsfile = metaFN($ns,'/.mlist');
  1193. if (@file_exists($nsfile)) {
  1194. $mlist = file($nsfile);
  1195. foreach ($mlist as $who) {
  1196. $who = rtrim($who);
  1197. if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
  1198. $users[$who] = true;
  1199. }
  1200. }
  1201. $ns = getNS($ns);
  1202. }
  1203. // root namespace
  1204. $nsfile = metaFN('','.mlist');
  1205. if (@file_exists($nsfile)) {
  1206. $mlist = file($nsfile);
  1207. foreach ($mlist as $who) {
  1208. $who = rtrim($who);
  1209. if(!$self && $who == $_SERVER['REMOTE_USER']) continue;
  1210. $users[$who] = true;
  1211. }
  1212. }
  1213. if(!empty($users)) {
  1214. foreach (array_keys($users) as $who) {
  1215. $info = $auth->getUserData($who);
  1216. if($info === false) continue;
  1217. $level = auth_aclcheck($id,$who,$info['grps']);
  1218. if ($level >= AUTH_READ) {
  1219. if (strcasecmp($info['mail'],$conf['notify']) != 0) {
  1220. $emails[] = $info['mail'];
  1221. }
  1222. }
  1223. }
  1224. }
  1225. return implode(',',$emails);
  1226. }
  1227. /**
  1228. * Removes quoting backslashes
  1229. *
  1230. * @author Andreas Gohr <andi@splitbrain.org>
  1231. */
  1232. function unslash($string,$char="'"){
  1233. return str_replace('\\'.$char,$char,$string);
  1234. }
  1235. /**
  1236. * Convert php.ini shorthands to byte
  1237. *
  1238. * @author <gilthans dot NO dot SPAM at gmail dot com>
  1239. * @link http://de3.php.net/manual/en/ini.core.php#79564
  1240. */
  1241. function php_to_byte($v){
  1242. $l = substr($v, -1);
  1243. $ret = substr($v, 0, -1);
  1244. switch(strtoupper($l)){
  1245. case 'P':
  1246. $ret *= 1024;
  1247. case 'T':
  1248. $ret *= 1024;
  1249. case 'G':
  1250. $ret *= 1024;
  1251. case 'M':
  1252. $ret *= 1024;
  1253. case 'K':
  1254. $ret *= 1024;
  1255. break;
  1256. }
  1257. return $ret;
  1258. }
  1259. /**
  1260. * Wrapper around preg_quote adding the default delimiter
  1261. */
  1262. function preg_quote_cb($string){
  1263. return preg_quote($string,'/');
  1264. }
  1265. /**
  1266. * Shorten a given string by removing data from the middle
  1267. *
  1268. * You can give the string in two parts, the first part $keep
  1269. * will never be shortened. The second part $short will be cut
  1270. * in the middle to shorten but only if at least $min chars are
  1271. * left to display it. Otherwise it will be left off.
  1272. *
  1273. * @param string $keep the part to keep
  1274. * @param string $short the part to shorten
  1275. * @param int $max maximum chars you want for the whole string
  1276. * @param int $min minimum number of chars to have left for middle shortening
  1277. * @param string $char the shortening character to use
  1278. */
  1279. function shorten($keep,$short,$max,$min=9,$char='…'){
  1280. $max = $max - utf8_strlen($keep);
  1281. if($max < $min) return $keep;
  1282. $len = utf8_strlen($short);
  1283. if($len <= $max) return $keep.$short;
  1284. $half = floor($max/2);
  1285. return $keep.utf8_substr($short,0,$half-1).$char.utf8_substr($short,$len-$half);
  1286. }
  1287. /**
  1288. * Return the users realname or e-mail address for use
  1289. * in page footer and recent changes pages
  1290. *
  1291. * @author Andy Webber <dokuwiki AT andywebber DOT com>
  1292. */
  1293. function editorinfo($username){
  1294. global $conf;
  1295. global $auth;
  1296. switch($conf['showuseras']){
  1297. case 'username':
  1298. case 'email':
  1299. case 'email_link':
  1300. if($auth) $info = $auth->getUserData($username);
  1301. break;
  1302. default:
  1303. return hsc($username);
  1304. }
  1305. if(isset($info) && $info) {
  1306. switch($conf['showuseras']){
  1307. case 'username':
  1308. return hsc($info['name']);
  1309. case 'email':
  1310. return obfuscate($info['mail']);
  1311. case 'email_link':
  1312. $mail=obfuscate($info['mail']);
  1313. return '<a href="mailto:'.$mail.'">'.$mail.'</a>';
  1314. default:
  1315. return hsc($username);
  1316. }
  1317. } else {
  1318. return hsc($username);
  1319. }
  1320. }
  1321. /**
  1322. * Returns the path to a image file for the currently chosen license.
  1323. * When no image exists, returns an empty string
  1324. *
  1325. * @author Andreas Gohr <andi@splitbrain.org>
  1326. * @param string $type - type of image 'badge' or 'button'
  1327. */
  1328. function license_img($type){
  1329. global $license;
  1330. global $conf;
  1331. if(!$conf['license']) return '';
  1332. if(!is_array($license[$conf['license']])) return '';
  1333. $lic = $license[$conf['license']];
  1334. $try = array();
  1335. $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.png';
  1336. $try[] = 'lib/images/license/'.$type.'/'.$conf['license'].'.gif';
  1337. if(substr($conf['license'],0,3) == 'cc-'){
  1338. $try[] = 'lib/images/license/'.$type.'/cc.png';
  1339. }
  1340. foreach($try as $src){
  1341. if(@file_exists(DOKU_INC.$src)) return $src;
  1342. }
  1343. return '';
  1344. }
  1345. /**
  1346. * Checks if the given amount of memory is available
  1347. *
  1348. * If the memory_get_usage() function is not available the
  1349. * function just assumes $bytes of already allocated memory
  1350. *
  1351. * @param int $mem Size of memory you want to allocate in bytes
  1352. * @param int $used already allocated memory (see above)
  1353. * @author Filip Oscadal <webmaster@illusionsoftworks.cz>
  1354. * @author Andreas Gohr <andi@splitbrain.org>
  1355. */
  1356. function is_mem_available($mem,$bytes=1048576){
  1357. $limit = trim(ini_get('memory_limit'));
  1358. if(empty($limit)) return true; // no limit set!
  1359. // parse limit to bytes
  1360. $limit = php_to_byte($limit);
  1361. // get used memory if possible
  1362. if(function_exists('memory_get_usage')){
  1363. $used = memory_get_usage();
  1364. }
  1365. if($used+$mem > $limit){
  1366. return false;
  1367. }
  1368. return true;
  1369. }
  1370. /**
  1371. * Send a HTTP redirect to the browser
  1372. *
  1373. * Works arround Microsoft IIS cookie sending bug. Exits the script.
  1374. *
  1375. * @link http://support.microsoft.com/kb/q176113/
  1376. * @author Andreas Gohr <andi@splitbrain.org>
  1377. */
  1378. function send_redirect($url){
  1379. // always close the session
  1380. session_write_close();
  1381. // check if running on IIS < 6 with CGI-PHP
  1382. if( isset($_SERVER['SERVER_SOFTWARE']) && isset($_SERVER['GATEWAY_INTERFACE']) &&
  1383. (strpos($_SERVER['GATEWAY_INTERFACE'],'CGI') !== false) &&
  1384. (preg_match('|^Microsoft-IIS/(\d)\.\d$|', trim($_SERVER['SERVER_SOFTWARE']), $matches)) &&
  1385. $matches[1] < 6 ){
  1386. header('Refresh: 0;url='.$url);
  1387. }else{
  1388. header('Location: '.$url);
  1389. }
  1390. exit;
  1391. }
  1392. //Setup VIM: ex: et ts=2 enc=utf-8 :