io.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. <?php
  2. /**
  3. * File IO functions
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Andreas Gohr <andi@splitbrain.org>
  7. */
  8. use dokuwiki\HTTP\DokuHTTPClient;
  9. use dokuwiki\Extension\Event;
  10. /**
  11. * Removes empty directories
  12. *
  13. * Sends IO_NAMESPACE_DELETED events for 'pages' and 'media' namespaces.
  14. * Event data:
  15. * $data[0] ns: The colon separated namespace path minus the trailing page name.
  16. * $data[1] ns_type: 'pages' or 'media' namespace tree.
  17. *
  18. * @param string $id - a pageid, the namespace of that id will be tried to deleted
  19. * @param string $basedir - the config name of the type to delete (datadir or mediadir usally)
  20. * @return bool - true if at least one namespace was deleted
  21. *
  22. * @author Andreas Gohr <andi@splitbrain.org>
  23. * @author Ben Coburn <btcoburn@silicodon.net>
  24. */
  25. function io_sweepNS($id,$basedir='datadir'){
  26. global $conf;
  27. $types = array ('datadir'=>'pages', 'mediadir'=>'media');
  28. $ns_type = (isset($types[$basedir])?$types[$basedir]:false);
  29. $delone = false;
  30. //scan all namespaces
  31. while(($id = getNS($id)) !== false){
  32. $dir = $conf[$basedir].'/'.utf8_encodeFN(str_replace(':','/',$id));
  33. //try to delete dir else return
  34. if(@rmdir($dir)) {
  35. if ($ns_type!==false) {
  36. $data = array($id, $ns_type);
  37. $delone = true; // we deleted at least one dir
  38. Event::createAndTrigger('IO_NAMESPACE_DELETED', $data);
  39. }
  40. } else { return $delone; }
  41. }
  42. return $delone;
  43. }
  44. /**
  45. * Used to read in a DokuWiki page from file, and send IO_WIKIPAGE_READ events.
  46. *
  47. * Generates the action event which delegates to io_readFile().
  48. * Action plugins are allowed to modify the page content in transit.
  49. * The file path should not be changed.
  50. *
  51. * Event data:
  52. * $data[0] The raw arguments for io_readFile as an array.
  53. * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns)
  54. * $data[2] page_name: The wiki page name.
  55. * $data[3] rev: The page revision, false for current wiki pages.
  56. *
  57. * @author Ben Coburn <btcoburn@silicodon.net>
  58. *
  59. * @param string $file filename
  60. * @param string $id page id
  61. * @param bool|int $rev revision timestamp
  62. * @return string
  63. */
  64. function io_readWikiPage($file, $id, $rev=false) {
  65. if (empty($rev)) { $rev = false; }
  66. $data = array(array($file, true), getNS($id), noNS($id), $rev);
  67. return Event::createAndTrigger('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false);
  68. }
  69. /**
  70. * Callback adapter for io_readFile().
  71. *
  72. * @author Ben Coburn <btcoburn@silicodon.net>
  73. *
  74. * @param array $data event data
  75. * @return string
  76. */
  77. function _io_readWikiPage_action($data) {
  78. if (is_array($data) && is_array($data[0]) && count($data[0])===2) {
  79. return call_user_func_array('io_readFile', $data[0]);
  80. } else {
  81. return ''; //callback error
  82. }
  83. }
  84. /**
  85. * Returns content of $file as cleaned string.
  86. *
  87. * Uses gzip if extension is .gz
  88. *
  89. * If you want to use the returned value in unserialize
  90. * be sure to set $clean to false!
  91. *
  92. * @author Andreas Gohr <andi@splitbrain.org>
  93. *
  94. * @param string $file filename
  95. * @param bool $clean
  96. * @return string|bool the file contents or false on error
  97. */
  98. function io_readFile($file,$clean=true){
  99. $ret = '';
  100. if(file_exists($file)){
  101. if(substr($file,-3) == '.gz'){
  102. if(!DOKU_HAS_GZIP) return false;
  103. $ret = gzfile($file);
  104. if(is_array($ret)) $ret = join('', $ret);
  105. }else if(substr($file,-4) == '.bz2'){
  106. if(!DOKU_HAS_BZIP) return false;
  107. $ret = bzfile($file);
  108. }else{
  109. $ret = file_get_contents($file);
  110. }
  111. }
  112. if($ret === null) return false;
  113. if($ret !== false && $clean){
  114. return cleanText($ret);
  115. }else{
  116. return $ret;
  117. }
  118. }
  119. /**
  120. * Returns the content of a .bz2 compressed file as string
  121. *
  122. * @author marcel senf <marcel@rucksackreinigung.de>
  123. * @author Andreas Gohr <andi@splitbrain.org>
  124. *
  125. * @param string $file filename
  126. * @param bool $array return array of lines
  127. * @return string|array|bool content or false on error
  128. */
  129. function bzfile($file, $array=false) {
  130. $bz = bzopen($file,"r");
  131. if($bz === false) return false;
  132. if($array) $lines = array();
  133. $str = '';
  134. while (!feof($bz)) {
  135. //8192 seems to be the maximum buffersize?
  136. $buffer = bzread($bz,8192);
  137. if(($buffer === false) || (bzerrno($bz) !== 0)) {
  138. return false;
  139. }
  140. $str = $str . $buffer;
  141. if($array) {
  142. $pos = strpos($str, "\n");
  143. while($pos !== false) {
  144. $lines[] = substr($str, 0, $pos+1);
  145. $str = substr($str, $pos+1);
  146. $pos = strpos($str, "\n");
  147. }
  148. }
  149. }
  150. bzclose($bz);
  151. if($array) {
  152. if($str !== '') $lines[] = $str;
  153. return $lines;
  154. }
  155. return $str;
  156. }
  157. /**
  158. * Used to write out a DokuWiki page to file, and send IO_WIKIPAGE_WRITE events.
  159. *
  160. * This generates an action event and delegates to io_saveFile().
  161. * Action plugins are allowed to modify the page content in transit.
  162. * The file path should not be changed.
  163. * (The append parameter is set to false.)
  164. *
  165. * Event data:
  166. * $data[0] The raw arguments for io_saveFile as an array.
  167. * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns)
  168. * $data[2] page_name: The wiki page name.
  169. * $data[3] rev: The page revision, false for current wiki pages.
  170. *
  171. * @author Ben Coburn <btcoburn@silicodon.net>
  172. *
  173. * @param string $file filename
  174. * @param string $content
  175. * @param string $id page id
  176. * @param int|bool $rev timestamp of revision
  177. * @return bool
  178. */
  179. function io_writeWikiPage($file, $content, $id, $rev=false) {
  180. if (empty($rev)) { $rev = false; }
  181. if ($rev===false) { io_createNamespace($id); } // create namespaces as needed
  182. $data = array(array($file, $content, false), getNS($id), noNS($id), $rev);
  183. return Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false);
  184. }
  185. /**
  186. * Callback adapter for io_saveFile().
  187. * @author Ben Coburn <btcoburn@silicodon.net>
  188. *
  189. * @param array $data event data
  190. * @return bool
  191. */
  192. function _io_writeWikiPage_action($data) {
  193. if (is_array($data) && is_array($data[0]) && count($data[0])===3) {
  194. $ok = call_user_func_array('io_saveFile', $data[0]);
  195. // for attic files make sure the file has the mtime of the revision
  196. if($ok && is_int($data[3]) && $data[3] > 0) {
  197. @touch($data[0][0], $data[3]);
  198. }
  199. return $ok;
  200. } else {
  201. return false; //callback error
  202. }
  203. }
  204. /**
  205. * Internal function to save contents to a file.
  206. *
  207. * @author Andreas Gohr <andi@splitbrain.org>
  208. *
  209. * @param string $file filename path to file
  210. * @param string $content
  211. * @param bool $append
  212. * @return bool true on success, otherwise false
  213. */
  214. function _io_saveFile($file, $content, $append) {
  215. global $conf;
  216. $mode = ($append) ? 'ab' : 'wb';
  217. $fileexists = file_exists($file);
  218. if(substr($file,-3) == '.gz'){
  219. if(!DOKU_HAS_GZIP) return false;
  220. $fh = @gzopen($file,$mode.'9');
  221. if(!$fh) return false;
  222. gzwrite($fh, $content);
  223. gzclose($fh);
  224. }else if(substr($file,-4) == '.bz2'){
  225. if(!DOKU_HAS_BZIP) return false;
  226. if($append) {
  227. $bzcontent = bzfile($file);
  228. if($bzcontent === false) return false;
  229. $content = $bzcontent.$content;
  230. }
  231. $fh = @bzopen($file,'w');
  232. if(!$fh) return false;
  233. bzwrite($fh, $content);
  234. bzclose($fh);
  235. }else{
  236. $fh = @fopen($file,$mode);
  237. if(!$fh) return false;
  238. fwrite($fh, $content);
  239. fclose($fh);
  240. }
  241. if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
  242. return true;
  243. }
  244. /**
  245. * Saves $content to $file.
  246. *
  247. * If the third parameter is set to true the given content
  248. * will be appended.
  249. *
  250. * Uses gzip if extension is .gz
  251. * and bz2 if extension is .bz2
  252. *
  253. * @author Andreas Gohr <andi@splitbrain.org>
  254. *
  255. * @param string $file filename path to file
  256. * @param string $content
  257. * @param bool $append
  258. * @return bool true on success, otherwise false
  259. */
  260. function io_saveFile($file, $content, $append=false) {
  261. io_makeFileDir($file);
  262. io_lock($file);
  263. if(!_io_saveFile($file, $content, $append)) {
  264. msg("Writing $file failed",-1);
  265. io_unlock($file);
  266. return false;
  267. }
  268. io_unlock($file);
  269. return true;
  270. }
  271. /**
  272. * Replace one or more occurrences of a line in a file.
  273. *
  274. * The default, when $maxlines is 0 is to delete all matching lines then append a single line.
  275. * A regex that matches any part of the line will remove the entire line in this mode.
  276. * Captures in $newline are not available.
  277. *
  278. * Otherwise each line is matched and replaced individually, up to the first $maxlines lines
  279. * or all lines if $maxlines is -1. If $regex is true then captures can be used in $newline.
  280. *
  281. * Be sure to include the trailing newline in $oldline when replacing entire lines.
  282. *
  283. * Uses gzip if extension is .gz
  284. * and bz2 if extension is .bz2
  285. *
  286. * @author Steven Danz <steven-danz@kc.rr.com>
  287. * @author Christopher Smith <chris@jalakai.co.uk>
  288. * @author Patrick Brown <ptbrown@whoopdedo.org>
  289. *
  290. * @param string $file filename
  291. * @param string $oldline exact linematch to remove
  292. * @param string $newline new line to insert
  293. * @param bool $regex use regexp?
  294. * @param int $maxlines number of occurrences of the line to replace
  295. * @return bool true on success
  296. */
  297. function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) {
  298. if ((string)$oldline === '') {
  299. trigger_error('$oldline parameter cannot be empty in io_replaceInFile()', E_USER_WARNING);
  300. return false;
  301. }
  302. if (!file_exists($file)) return true;
  303. io_lock($file);
  304. // load into array
  305. if(substr($file,-3) == '.gz'){
  306. if(!DOKU_HAS_GZIP) return false;
  307. $lines = gzfile($file);
  308. }else if(substr($file,-4) == '.bz2'){
  309. if(!DOKU_HAS_BZIP) return false;
  310. $lines = bzfile($file, true);
  311. }else{
  312. $lines = file($file);
  313. }
  314. // make non-regexes into regexes
  315. $pattern = $regex ? $oldline : '/^'.preg_quote($oldline,'/').'$/';
  316. $replace = $regex ? $newline : addcslashes($newline, '\$');
  317. // remove matching lines
  318. if ($maxlines > 0) {
  319. $count = 0;
  320. $matched = 0;
  321. foreach($lines as $i => $line) {
  322. if($count >= $maxlines) break;
  323. // $matched will be set to 0|1 depending on whether pattern is matched and line replaced
  324. $lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched);
  325. if ($matched) $count++;
  326. }
  327. } else if ($maxlines == 0) {
  328. $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
  329. if ((string)$newline !== ''){
  330. $lines[] = $newline;
  331. }
  332. } else {
  333. $lines = preg_replace($pattern, $replace, $lines);
  334. }
  335. if(count($lines)){
  336. if(!_io_saveFile($file, join('',$lines), false)) {
  337. msg("Removing content from $file failed",-1);
  338. io_unlock($file);
  339. return false;
  340. }
  341. }else{
  342. @unlink($file);
  343. }
  344. io_unlock($file);
  345. return true;
  346. }
  347. /**
  348. * Delete lines that match $badline from $file.
  349. *
  350. * Be sure to include the trailing newline in $badline
  351. *
  352. * @author Patrick Brown <ptbrown@whoopdedo.org>
  353. *
  354. * @param string $file filename
  355. * @param string $badline exact linematch to remove
  356. * @param bool $regex use regexp?
  357. * @return bool true on success
  358. */
  359. function io_deleteFromFile($file,$badline,$regex=false){
  360. return io_replaceInFile($file,$badline,null,$regex,0);
  361. }
  362. /**
  363. * Tries to lock a file
  364. *
  365. * Locking is only done for io_savefile and uses directories
  366. * inside $conf['lockdir']
  367. *
  368. * It waits maximal 3 seconds for the lock, after this time
  369. * the lock is assumed to be stale and the function goes on
  370. *
  371. * @author Andreas Gohr <andi@splitbrain.org>
  372. *
  373. * @param string $file filename
  374. */
  375. function io_lock($file){
  376. global $conf;
  377. $lockDir = $conf['lockdir'].'/'.md5($file);
  378. @ignore_user_abort(1);
  379. $timeStart = time();
  380. do {
  381. //waited longer than 3 seconds? -> stale lock
  382. if ((time() - $timeStart) > 3) break;
  383. $locked = @mkdir($lockDir);
  384. if($locked){
  385. if($conf['dperm']) chmod($lockDir, $conf['dperm']);
  386. break;
  387. }
  388. usleep(50);
  389. } while ($locked === false);
  390. }
  391. /**
  392. * Unlocks a file
  393. *
  394. * @author Andreas Gohr <andi@splitbrain.org>
  395. *
  396. * @param string $file filename
  397. */
  398. function io_unlock($file){
  399. global $conf;
  400. $lockDir = $conf['lockdir'].'/'.md5($file);
  401. @rmdir($lockDir);
  402. @ignore_user_abort(0);
  403. }
  404. /**
  405. * Create missing namespace directories and send the IO_NAMESPACE_CREATED events
  406. * in the order of directory creation. (Parent directories first.)
  407. *
  408. * Event data:
  409. * $data[0] ns: The colon separated namespace path minus the trailing page name.
  410. * $data[1] ns_type: 'pages' or 'media' namespace tree.
  411. *
  412. * @author Ben Coburn <btcoburn@silicodon.net>
  413. *
  414. * @param string $id page id
  415. * @param string $ns_type 'pages' or 'media'
  416. */
  417. function io_createNamespace($id, $ns_type='pages') {
  418. // verify ns_type
  419. $types = array('pages'=>'wikiFN', 'media'=>'mediaFN');
  420. if (!isset($types[$ns_type])) {
  421. trigger_error('Bad $ns_type parameter for io_createNamespace().');
  422. return;
  423. }
  424. // make event list
  425. $missing = array();
  426. $ns_stack = explode(':', $id);
  427. $ns = $id;
  428. $tmp = dirname( $file = call_user_func($types[$ns_type], $ns) );
  429. while (!@is_dir($tmp) && !(file_exists($tmp) && !is_dir($tmp))) {
  430. array_pop($ns_stack);
  431. $ns = implode(':', $ns_stack);
  432. if (strlen($ns)==0) { break; }
  433. $missing[] = $ns;
  434. $tmp = dirname(call_user_func($types[$ns_type], $ns));
  435. }
  436. // make directories
  437. io_makeFileDir($file);
  438. // send the events
  439. $missing = array_reverse($missing); // inside out
  440. foreach ($missing as $ns) {
  441. $data = array($ns, $ns_type);
  442. Event::createAndTrigger('IO_NAMESPACE_CREATED', $data);
  443. }
  444. }
  445. /**
  446. * Create the directory needed for the given file
  447. *
  448. * @author Andreas Gohr <andi@splitbrain.org>
  449. *
  450. * @param string $file file name
  451. */
  452. function io_makeFileDir($file){
  453. $dir = dirname($file);
  454. if(!@is_dir($dir)){
  455. io_mkdir_p($dir) || msg("Creating directory $dir failed",-1);
  456. }
  457. }
  458. /**
  459. * Creates a directory hierachy.
  460. *
  461. * @link http://php.net/manual/en/function.mkdir.php
  462. * @author <saint@corenova.com>
  463. * @author Andreas Gohr <andi@splitbrain.org>
  464. *
  465. * @param string $target filename
  466. * @return bool|int|string
  467. */
  468. function io_mkdir_p($target){
  469. global $conf;
  470. if (@is_dir($target)||empty($target)) return 1; // best case check first
  471. if (file_exists($target) && !is_dir($target)) return 0;
  472. //recursion
  473. if (io_mkdir_p(substr($target,0,strrpos($target,'/')))){
  474. $ret = @mkdir($target); // crawl back up & create dir tree
  475. if($ret && !empty($conf['dperm'])) chmod($target, $conf['dperm']);
  476. return $ret;
  477. }
  478. return 0;
  479. }
  480. /**
  481. * Recursively delete a directory
  482. *
  483. * @author Andreas Gohr <andi@splitbrain.org>
  484. * @param string $path
  485. * @param bool $removefiles defaults to false which will delete empty directories only
  486. * @return bool
  487. */
  488. function io_rmdir($path, $removefiles = false) {
  489. if(!is_string($path) || $path == "") return false;
  490. if(!file_exists($path)) return true; // it's already gone or was never there, count as success
  491. if(is_dir($path) && !is_link($path)) {
  492. $dirs = array();
  493. $files = array();
  494. if(!$dh = @opendir($path)) return false;
  495. while(false !== ($f = readdir($dh))) {
  496. if($f == '..' || $f == '.') continue;
  497. // collect dirs and files first
  498. if(is_dir("$path/$f") && !is_link("$path/$f")) {
  499. $dirs[] = "$path/$f";
  500. } else if($removefiles) {
  501. $files[] = "$path/$f";
  502. } else {
  503. return false; // abort when non empty
  504. }
  505. }
  506. closedir($dh);
  507. // now traverse into directories first
  508. foreach($dirs as $dir) {
  509. if(!io_rmdir($dir, $removefiles)) return false; // abort on any error
  510. }
  511. // now delete files
  512. foreach($files as $file) {
  513. if(!@unlink($file)) return false; //abort on any error
  514. }
  515. // remove self
  516. return @rmdir($path);
  517. } else if($removefiles) {
  518. return @unlink($path);
  519. }
  520. return false;
  521. }
  522. /**
  523. * Creates a unique temporary directory and returns
  524. * its path.
  525. *
  526. * @author Michael Klier <chi@chimeric.de>
  527. *
  528. * @return false|string path to new directory or false
  529. */
  530. function io_mktmpdir() {
  531. global $conf;
  532. $base = $conf['tmpdir'];
  533. $dir = md5(uniqid(mt_rand(), true));
  534. $tmpdir = $base.'/'.$dir;
  535. if(io_mkdir_p($tmpdir)) {
  536. return($tmpdir);
  537. } else {
  538. return false;
  539. }
  540. }
  541. /**
  542. * downloads a file from the net and saves it
  543. *
  544. * if $useAttachment is false,
  545. * - $file is the full filename to save the file, incl. path
  546. * - if successful will return true, false otherwise
  547. *
  548. * if $useAttachment is true,
  549. * - $file is the directory where the file should be saved
  550. * - if successful will return the name used for the saved file, false otherwise
  551. *
  552. * @author Andreas Gohr <andi@splitbrain.org>
  553. * @author Chris Smith <chris@jalakai.co.uk>
  554. *
  555. * @param string $url url to download
  556. * @param string $file path to file or directory where to save
  557. * @param bool $useAttachment true: try to use name of download, uses otherwise $defaultName
  558. * false: uses $file as path to file
  559. * @param string $defaultName fallback for if using $useAttachment
  560. * @param int $maxSize maximum file size
  561. * @return bool|string if failed false, otherwise true or the name of the file in the given dir
  562. */
  563. function io_download($url,$file,$useAttachment=false,$defaultName='',$maxSize=2097152){
  564. global $conf;
  565. $http = new DokuHTTPClient();
  566. $http->max_bodysize = $maxSize;
  567. $http->timeout = 25; //max. 25 sec
  568. $http->keep_alive = false; // we do single ops here, no need for keep-alive
  569. $data = $http->get($url);
  570. if(!$data) return false;
  571. $name = '';
  572. if ($useAttachment) {
  573. if (isset($http->resp_headers['content-disposition'])) {
  574. $content_disposition = $http->resp_headers['content-disposition'];
  575. $match=array();
  576. if (is_string($content_disposition) &&
  577. preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) {
  578. $name = \dokuwiki\Utf8\PhpString::basename($match[1]);
  579. }
  580. }
  581. if (!$name) {
  582. if (!$defaultName) return false;
  583. $name = $defaultName;
  584. }
  585. $file = $file.$name;
  586. }
  587. $fileexists = file_exists($file);
  588. $fp = @fopen($file,"w");
  589. if(!$fp) return false;
  590. fwrite($fp,$data);
  591. fclose($fp);
  592. if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
  593. if ($useAttachment) return $name;
  594. return true;
  595. }
  596. /**
  597. * Windows compatible rename
  598. *
  599. * rename() can not overwrite existing files on Windows
  600. * this function will use copy/unlink instead
  601. *
  602. * @param string $from
  603. * @param string $to
  604. * @return bool succes or fail
  605. */
  606. function io_rename($from,$to){
  607. global $conf;
  608. if(!@rename($from,$to)){
  609. if(@copy($from,$to)){
  610. if($conf['fperm']) chmod($to, $conf['fperm']);
  611. @unlink($from);
  612. return true;
  613. }
  614. return false;
  615. }
  616. return true;
  617. }
  618. /**
  619. * Runs an external command with input and output pipes.
  620. * Returns the exit code from the process.
  621. *
  622. * @author Tom N Harris <tnharris@whoopdedo.org>
  623. *
  624. * @param string $cmd
  625. * @param string $input input pipe
  626. * @param string $output output pipe
  627. * @return int exit code from process
  628. */
  629. function io_exec($cmd, $input, &$output){
  630. $descspec = array(
  631. 0=>array("pipe","r"),
  632. 1=>array("pipe","w"),
  633. 2=>array("pipe","w"));
  634. $ph = proc_open($cmd, $descspec, $pipes);
  635. if(!$ph) return -1;
  636. fclose($pipes[2]); // ignore stderr
  637. fwrite($pipes[0], $input);
  638. fclose($pipes[0]);
  639. $output = stream_get_contents($pipes[1]);
  640. fclose($pipes[1]);
  641. return proc_close($ph);
  642. }
  643. /**
  644. * Search a file for matching lines
  645. *
  646. * This is probably not faster than file()+preg_grep() but less
  647. * memory intensive because not the whole file needs to be loaded
  648. * at once.
  649. *
  650. * @author Andreas Gohr <andi@splitbrain.org>
  651. * @param string $file The file to search
  652. * @param string $pattern PCRE pattern
  653. * @param int $max How many lines to return (0 for all)
  654. * @param bool $backref When true returns array with backreferences instead of lines
  655. * @return array matching lines or backref, false on error
  656. */
  657. function io_grep($file,$pattern,$max=0,$backref=false){
  658. $fh = @fopen($file,'r');
  659. if(!$fh) return false;
  660. $matches = array();
  661. $cnt = 0;
  662. $line = '';
  663. while (!feof($fh)) {
  664. $line .= fgets($fh, 4096); // read full line
  665. if(substr($line,-1) != "\n") continue;
  666. // check if line matches
  667. if(preg_match($pattern,$line,$match)){
  668. if($backref){
  669. $matches[] = $match;
  670. }else{
  671. $matches[] = $line;
  672. }
  673. $cnt++;
  674. }
  675. if($max && $max == $cnt) break;
  676. $line = '';
  677. }
  678. fclose($fh);
  679. return $matches;
  680. }
  681. /**
  682. * Get size of contents of a file, for a compressed file the uncompressed size
  683. * Warning: reading uncompressed size of content of bz-files requires uncompressing
  684. *
  685. * @author Gerrit Uitslag <klapinklapin@gmail.com>
  686. *
  687. * @param string $file filename path to file
  688. * @return int size of file
  689. */
  690. function io_getSizeFile($file) {
  691. if (!file_exists($file)) return 0;
  692. if(substr($file,-3) == '.gz'){
  693. $fp = @fopen($file, "rb");
  694. if($fp === false) return 0;
  695. fseek($fp, -4, SEEK_END);
  696. $buffer = fread($fp, 4);
  697. fclose($fp);
  698. $array = unpack("V", $buffer);
  699. $uncompressedsize = end($array);
  700. }else if(substr($file,-4) == '.bz2'){
  701. if(!DOKU_HAS_BZIP) return 0;
  702. $bz = bzopen($file,"r");
  703. if($bz === false) return 0;
  704. $uncompressedsize = 0;
  705. while (!feof($bz)) {
  706. //8192 seems to be the maximum buffersize?
  707. $buffer = bzread($bz,8192);
  708. if(($buffer === false) || (bzerrno($bz) !== 0)) {
  709. return 0;
  710. }
  711. $uncompressedsize += strlen($buffer);
  712. }
  713. }else{
  714. $uncompressedsize = filesize($file);
  715. }
  716. return $uncompressedsize;
  717. }