Completed
Push — nozlib ( a04611...a8d259 )
by Andreas
11:20 queued 05:17
created

inc/io.php (5 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
652
    $http->timeout = 25; //max. 25 sec
0 ignored issues
show
The property timeout cannot be accessed from this context as it is declared private in class HTTPClient.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
653
    $http->keep_alive = false; // we do single ops here, no need for keep-alive
0 ignored issues
show
The property keep_alive cannot be accessed from this context as it is declared private in class HTTPClient.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
654
655
    $data = $http->get($url);
656
    if(!$data) return false;
657
658
    $name = '';
659
    if ($useAttachment) {
660
        if (isset($http->resp_headers['content-disposition'])) {
0 ignored issues
show
The property resp_headers cannot be accessed from this context as it is declared private in class HTTPClient.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
661
            $content_disposition = $http->resp_headers['content-disposition'];
0 ignored issues
show
The property resp_headers cannot be accessed from this context as it is declared private in class HTTPClient.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
662
            $match=array();
663
            if (is_string($content_disposition) &&
664
                    preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) {
665
666
                $name = utf8_basename($match[1]);
667
            }
668
669
        }
670
671
        if (!$name) {
672
            if (!$defaultName) return false;
673
            $name = $defaultName;
674
        }
675
676
        $file = $file.$name;
677
    }
678
679
    $fileexists = file_exists($file);
680
    $fp = @fopen($file,"w");
681
    if(!$fp) return false;
682
    fwrite($fp,$data);
683
    fclose($fp);
684
    if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
685
    if ($useAttachment) return $name;
686
    return true;
687
}
688
689
/**
690
 * Windows compatible rename
691
 *
692
 * rename() can not overwrite existing files on Windows
693
 * this function will use copy/unlink instead
694
 *
695
 * @param string $from
696
 * @param string $to
697
 * @return bool succes or fail
698
 */
699
function io_rename($from,$to){
700
    global $conf;
701
    if(!@rename($from,$to)){
702
        if(@copy($from,$to)){
703
            if($conf['fperm']) chmod($to, $conf['fperm']);
704
            @unlink($from);
705
            return true;
706
        }
707
        return false;
708
    }
709
    return true;
710
}
711
712
/**
713
 * Runs an external command with input and output pipes.
714
 * Returns the exit code from the process.
715
 *
716
 * @author Tom N Harris <[email protected]>
717
 *
718
 * @param string $cmd
719
 * @param string $input  input pipe
720
 * @param string $output output pipe
721
 * @return int exit code from process
722
 */
723
function io_exec($cmd, $input, &$output){
724
    $descspec = array(
725
            0=>array("pipe","r"),
726
            1=>array("pipe","w"),
727
            2=>array("pipe","w"));
728
    $ph = proc_open($cmd, $descspec, $pipes);
729
    if(!$ph) return -1;
730
    fclose($pipes[2]); // ignore stderr
731
    fwrite($pipes[0], $input);
732
    fclose($pipes[0]);
733
    $output = stream_get_contents($pipes[1]);
734
    fclose($pipes[1]);
735
    return proc_close($ph);
736
}
737
738
/**
739
 * Search a file for matching lines
740
 *
741
 * This is probably not faster than file()+preg_grep() but less
742
 * memory intensive because not the whole file needs to be loaded
743
 * at once.
744
 *
745
 * @author Andreas Gohr <[email protected]>
746
 * @param  string $file    The file to search
747
 * @param  string $pattern PCRE pattern
748
 * @param  int    $max     How many lines to return (0 for all)
749
 * @param  bool   $backref When true returns array with backreferences instead of lines
750
 * @return array matching lines or backref, false on error
751
 */
752
function io_grep($file,$pattern,$max=0,$backref=false){
753
    $fh = @fopen($file,'r');
754
    if(!$fh) return false;
755
    $matches = array();
756
757
    $cnt  = 0;
758
    $line = '';
759
    while (!feof($fh)) {
760
        $line .= fgets($fh, 4096);  // read full line
761
        if(substr($line,-1) != "\n") continue;
762
763
        // check if line matches
764
        if(preg_match($pattern,$line,$match)){
765
            if($backref){
766
                $matches[] = $match;
767
            }else{
768
                $matches[] = $line;
769
            }
770
            $cnt++;
771
        }
772
        if($max && $max == $cnt) break;
773
        $line = '';
774
    }
775
    fclose($fh);
776
    return $matches;
777
}
778
779