Test Failed
Push — master ( 1f9a79...0fd1be )
by Sebastian
08:36
created

FileHelper::saveFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 1
c 3
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php
2
/**
3
 * File containing the {@see AppUtils\FileHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage FileHelper
7
 * @see FileHelper
8
 */
9
10
namespace AppUtils;
11
12
use AppUtils\FileHelper\AbstractPathInfo;
13
use AppUtils\FileHelper\CLICommandChecker;
14
use AppUtils\FileHelper\FileDownloader;
15
use AppUtils\FileHelper\FileFinder;
16
use AppUtils\FileHelper\FileInfo\NameFixer;
17
use AppUtils\FileHelper\PathRelativizer;
18
use AppUtils\FileHelper\PathsReducer;
19
use AppUtils\FileHelper\FolderInfo;
20
use AppUtils\FileHelper\FolderTree;
21
use AppUtils\FileHelper\FileInfo;
22
use AppUtils\FileHelper\JSONFile;
23
use AppUtils\FileHelper\PathInfoInterface;
24
use AppUtils\FileHelper\SerializedFile;
25
use AppUtils\FileHelper\UnicodeHandling;
26
use AppUtils\FileHelper\UploadFileSizeInfo;
27
use DateTime;
28
use DirectoryIterator;
29
use ParseCsv\Csv;
30
31
/**
32
 * Collection of file system related methods.
33
 * 
34
 * @package Application Utils
35
 * @subpackage FileHelper
36
 * @author Sebastian Mordziol <[email protected]>
37
 */
38
class FileHelper
39
{
40
    public const ERROR_CANNOT_FIND_JSON_FILE = 340001;
41
    public const ERROR_JSON_FILE_CANNOT_BE_READ = 340002;
42
    public const ERROR_CANNOT_DECODE_JSON_FILE = 340003;
43
    public const ERROR_JSON_ENCODE_ERROR = 340005;
44
    public const ERROR_CANNOT_OPEN_URL = 340008;
45
    public const ERROR_CANNOT_CREATE_FOLDER = 340009;
46
    public const ERROR_FILE_NOT_READABLE = 340010;
47
    public const ERROR_CANNOT_COPY_FILE = 340011;
48
    public const ERROR_CANNOT_DELETE_FILE = 340012;
49
    public const ERROR_FIND_SUBFOLDERS_FOLDER_DOES_NOT_EXIST = 340014;
50
    public const ERROR_UNKNOWN_FILE_MIME_TYPE = 340015;
51
    public const ERROR_SERIALIZED_FILE_CANNOT_BE_READ = 340017;
52
    public const ERROR_SERIALIZED_FILE_UNSERIALZE_FAILED = 340018;
53
    public const ERROR_UNSUPPORTED_OS_CLI_COMMAND = 340019;
54
    public const ERROR_SOURCE_FILE_NOT_FOUND = 340020;
55
    public const ERROR_SOURCE_FILE_NOT_READABLE = 340021;
56
    public const ERROR_TARGET_COPY_FOLDER_NOT_WRITABLE = 340022;
57
    public const ERROR_SAVE_FOLDER_NOT_WRITABLE = 340023;
58
    public const ERROR_SAVE_FILE_NOT_WRITABLE = 340024;
59
    public const ERROR_SAVE_FILE_WRITE_FAILED = 340025;
60
    public const ERROR_FILE_DOES_NOT_EXIST = 340026;
61
    public const ERROR_CANNOT_OPEN_FILE_TO_READ_LINES = 340027;
62
    public const ERROR_CANNOT_READ_FILE_CONTENTS = 340028;
63
    public const ERROR_PARSING_CSV = 340029;
64
    public const ERROR_CURL_INIT_FAILED = 340030;
65
    public const ERROR_CURL_OUTPUT_NOT_STRING = 340031;
66
    public const ERROR_CANNOT_OPEN_FILE_TO_DETECT_BOM = 340032;
67
    public const ERROR_FOLDER_DOES_NOT_EXIST = 340033;
68
    public const ERROR_PATH_IS_NOT_A_FOLDER = 340034;
69
    public const ERROR_CANNOT_WRITE_TO_FOLDER = 340035;
70
    public const ERROR_CANNOT_DELETE_FOLDER = 340036;
71
    public const ERROR_REAL_PATH_NOT_FOUND = 340037;
72
    public const ERROR_PATH_IS_NOT_A_FILE = 340038;
73
    public const ERROR_PATH_NOT_WRITABLE = 340039;
74
    public const ERROR_PATH_INVALID = 340040;
75
76
   /**
77
    * Opens a serialized file and returns the unserialized data.
78
    *
79
    * @param string $file
80
    * @throws FileHelper_Exception
81
    * @return array<int|string,mixed>
82
    * @see SerializedFile::parse()
83
    * 
84
    * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
85
    * @see FileHelper::ERROR_SERIALIZED_FILE_CANNOT_BE_READ
86
    * @see FileHelper::ERROR_SERIALIZED_FILE_UNSERIALZE_FAILED
87
    */
88
    public static function parseSerializedFile(string $file) : array
89
    {
90
        return SerializedFile::factory(self::getFileInfo($file))
91
            ->parse();
92
    }
93
94
    /**
95
     * Deletes a folder tree with all files therein, including
96
     * the specified folder itself.
97
     *
98
     * @param string|PathInfoInterface|DirectoryIterator $rootFolder
99
     * @return bool
100
     * @throws FileHelper_Exception
101
     */
102
    public static function deleteTree($rootFolder) : bool
103
    {
104
        return FolderTree::delete($rootFolder);
105
    }
106
    
107
   /**
108
    * Create a folder, if it does not exist yet.
109
    *  
110
    * @param string|PathInfoInterface $path
111
    * @throws FileHelper_Exception
112
    * @see FileHelper::ERROR_CANNOT_CREATE_FOLDER
113
    */
114
    public static function createFolder($path) : FolderInfo
115
    {
116
        return self::getFolderInfo($path)->create();
117
    }
118
119
    /**
120
     * @param string|PathInfoInterface|DirectoryIterator $path
121
     * @return FolderInfo
122
     * @throws FileHelper_Exception
123
     */
124
    public static function getFolderInfo($path) : FolderInfo
125
    {
126
        return FolderInfo::factory($path);
127
    }
128
129
    /**
130
     * Copies a folder tree to the target folder.
131
     *
132
     * @param string|PathInfoInterface|DirectoryIterator $source
133
     * @param string|PathInfoInterface|DirectoryIterator $target
134
     * @throws FileHelper_Exception
135
     */
136
    public static function copyTree($source, $target) : void
137
    {
138
        FolderTree::copy($source, $target);
139
    }
140
    
141
   /**
142
    * Copies a file to the target location. Includes checks
143
    * for most error sources, like the source file not being
144
    * readable. Automatically creates the target folder if it
145
    * does not exist yet.
146
    * 
147
    * @param string $sourcePath
148
    * @param string $targetPath
149
    * @throws FileHelper_Exception
150
    * 
151
    * @see FileHelper::ERROR_CANNOT_CREATE_FOLDER
152
    * @see FileHelper::ERROR_SOURCE_FILE_NOT_FOUND
153
    * @see FileHelper::ERROR_SOURCE_FILE_NOT_READABLE
154
    * @see FileHelper::ERROR_TARGET_COPY_FOLDER_NOT_WRITABLE
155
    * @see FileHelper::ERROR_CANNOT_COPY_FILE
156
    */
157
    public static function copyFile(string $sourcePath, string $targetPath) : void
158
    {
159
        self::getFileInfo($sourcePath)->copyTo($targetPath);
160
    }
161
    
162
   /**
163
    * Deletes the target file. Ignored if it cannot be found,
164
    * and throws an exception if it fails.
165
    * 
166
    * @param string $filePath
167
    * @throws FileHelper_Exception
168
    * 
169
    * @see FileHelper::ERROR_CANNOT_DELETE_FILE
170
    */
171
    public static function deleteFile(string $filePath) : void
172
    {
173
        self::getFileInfo($filePath)->delete();
174
    }
175
176
    /**
177
     * Retrieves an instance of the file info class, which
178
     * allows file operations and accessing information on
179
     * the file.
180
     *
181
     * @param string|PathInfoInterface|DirectoryIterator $path
182
     * @return FileInfo
183
     * @throws FileHelper_Exception
184
     */
185
    public static function getFileInfo($path) : FileInfo
186
    {
187
        return FileInfo::factory($path);
188
    }
189
190
    /**
191
     * @param string|PathInfoInterface|DirectoryIterator $path
192
     * @return PathInfoInterface
193
     * @throws FileHelper_Exception
194
     */
195
    public static function getPathInfo($path) : PathInfoInterface
196
    {
197
        return AbstractPathInfo::resolveType($path);
198
    }
199
200
    /**
201
     * Detects the mime type for the specified file name/path.
202
     * Returns null if it is not a known file extension.
203
     *
204
     * @param string $fileName
205
     * @return string|NULL
206
     */
207
    public static function detectMimeType(string $fileName) : ?string
208
    {
209
        $ext = self::getExtension($fileName);
210
        if(empty($ext)) {
211
            return null;
212
        }
213
214
        return FileHelper_MimeTypes::getMime($ext);
215
    }
216
217
    /**
218
     * Like `sendFile()`, but automatically determines whether
219
     * the browser can open the target file type, to either
220
     * send it directly to the browser, or force downloading
221
     * it instead.
222
     *
223
     * @param string $filePath
224
     * @param string $fileName
225
     * @throws FileHelper_Exception
226
     */
227
    public function sendFileAuto(string $filePath, string $fileName = '') : void
228
    {
229
        self::sendFile(
230
            $filePath,
231
            $fileName,
232
            !FileHelper_MimeTypes::canBrowserDisplay(self::getExtension($filePath))
233
        );
234
    }
235
236
    /**
237
     * Detects the mime type of the target file automatically,
238
     * sends the required headers to trigger a download and
239
     * outputs the file. Returns false if the mime type could
240
     * not be determined.
241
     * 
242
     * @param string $filePath
243
     * @param string|null $fileName The name of the file for the client.
244
     * @param bool $asAttachment Whether to force the client to download the file.
245
     * @throws FileHelper_Exception
246
     * 
247
     * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
248
     * @see FileHelper::ERROR_UNKNOWN_FILE_MIME_TYPE
249
     */
250
    public static function sendFile(string $filePath, ?string $fileName = null, bool $asAttachment=true) : void
251
    {
252
        self::getFileInfo($filePath)->getDownloader()->send($fileName, $asAttachment);
253
    }
254
255
    /**
256
     * Uses cURL to download the contents of the specified URL,
257
     * returns the content.
258
     *
259
     * @param string $url
260
     * @param int $timeout In seconds. Set to 0 to use the default.
261
     * @param bool $SSLEnabled Whether to enable HTTPs host verification.
262
     * @return string
263
     *
264
     * @throws FileHelper_Exception
265
     * @see FileHelper::ERROR_CANNOT_OPEN_URL
266
     */
267
    public static function downloadFile(string $url, int $timeout=0, bool $SSLEnabled=false) : string
268
    {
269
        return FileDownloader::factory($url)
270
            ->setTimeout($timeout)
271
            ->setSSLEnabled($SSLEnabled)
272
            ->download();
273
    }
274
    
275
   /**
276
    * Verifies whether the target file is a PHP file. The path
277
    * to the file can be a path to a file as a string, or a 
278
    * DirectoryIterator object instance.
279
    * 
280
    * @param string|DirectoryIterator $pathOrDirIterator
281
    * @return boolean
282
    */
283
    public static function isPHPFile($pathOrDirIterator) : bool
284
    {
285
    	return self::getExtension($pathOrDirIterator) === 'php';
286
    }
287
    
288
   /**
289
    * Retrieves the extension of the specified file. Can be a path
290
    * to a file as a string, or a DirectoryIterator object instance.
291
    *
292
    * NOTE: A folder will return an empty string.
293
    * 
294
    * @param string|DirectoryIterator $pathOrDirIterator
295
    * @param bool $lowercase
296
    * @return string
297
    */
298
    public static function getExtension($pathOrDirIterator, bool $lowercase = true) : string
299
    {
300
        $info = self::getPathInfo($pathOrDirIterator);
301
302
        if($info instanceof FileInfo)
303
        {
304
            return $info->getExtension($lowercase);
305
        }
306
307
        return '';
308
    }
309
    
310
   /**
311
    * Retrieves the file name from a path, with or without extension.
312
    * The path to the file can be a string, or a DirectoryIterator object
313
    * instance.
314
    * 
315
    * In case of folders, behaves like the "pathinfo" function: returns
316
    * the name of the folder.
317
    * 
318
    * @param string|DirectoryIterator $pathOrDirIterator
319
    * @param bool $extension
320
    * @return string
321
    */
322
    public static function getFilename($pathOrDirIterator, bool $extension = true) : string
323
    {
324
        $info = self::getPathInfo($pathOrDirIterator);
325
326
        if($extension === true || $info instanceof FolderInfo)
327
        {
328
            return $info->getName();
329
        }
330
331
        return $info->requireIsFile()->removeExtension();
332
    }
333
334
    /**
335
     * Tries to read the contents of the target file and
336
     * treat it as JSON to return the decoded JSON data.
337
     *
338
     * @param string $file
339
     * @param string $targetEncoding
340
     * @param string|string[]|null $sourceEncoding
341
     * @return array<int|string,mixed>
342
     *
343
     * @throws FileHelper_Exception
344
     * @see FileHelper::ERROR_CANNOT_FIND_JSON_FILE
345
     * @see FileHelper::ERROR_CANNOT_DECODE_JSON_FILE
346
     */
347
    public static function parseJSONFile(string $file, string $targetEncoding='', $sourceEncoding=null) : array
348
    {
349
        return JSONFile::factory(self::getFileInfo($file))
350
            ->setTargetEncoding($targetEncoding)
351
            ->setSourceEncodings($sourceEncoding)
352
            ->parse();
353
    }
354
    
355
   /**
356
    * Corrects common formatting mistakes when users enter
357
    * file names, like too many spaces, dots and the like.
358
    * 
359
    * NOTE: if the file name contains a path, the path is
360
    * stripped, leaving only the file name.
361
    * 
362
    * @param string $name
363
    * @return string
364
    */
365
    public static function fixFileName(string $name) : string
366
    {
367
        return NameFixer::fixName($name);
368
    }
369
370
    /**
371
     * Creates an instance of the file finder, which is an easier
372
     * alternative to the other manual findFile methods, since all
373
     * options can be set by chaining.
374
     *
375
     * @param string|AbstractPathInfo|DirectoryIterator $path
376
     * @return FileFinder
377
     * @throws FileHelper_Exception
378
     *
379
     * @see FileFinder::ERROR_PATH_DOES_NOT_EXIST
380
     */
381
    public static function createFileFinder($path) : FileFinder
382
    {
383
        return new FileFinder($path);
384
    }
385
386
    /**
387
     * Searches for all HTML files in the target folder.
388
     *
389
     * NOTE: This method only exists for backwards compatibility.
390
     * Use the {@see FileHelper::createFileFinder()} method instead,
391
     * which offers an object-oriented interface that is much easier
392
     * to use.
393
     *
394
     * @param string $targetFolder
395
     * @param array<string,mixed> $options
396
     * @return string[] An indexed array with files.
397
     * @throws FileHelper_Exception
398
     * @see FileHelper::createFileFinder()
399
     */
400
    public static function findHTMLFiles(string $targetFolder, array $options=array()) : array
401
    {
402
        return self::findFiles($targetFolder, array('html'), $options);
0 ignored issues
show
Deprecated Code introduced by
The function AppUtils\FileHelper::findFiles() has been deprecated: Use the file finder instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

402
        return /** @scrutinizer ignore-deprecated */ self::findFiles($targetFolder, array('html'), $options);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
403
    }
404
405
    /**
406
     * Searches for all PHP files in the target folder.
407
     *
408
     * NOTE: This method only exists for backwards compatibility.
409
     * Use the {@see FileHelper::createFileFinder()} method instead,
410
     * which offers an object-oriented interface that is much easier
411
     * to use.
412
     *
413
     * @param string $targetFolder
414
     * @param array<string,mixed> $options
415
     * @return string[] An indexed array of PHP files.
416
     * @throws FileHelper_Exception
417
     * @see FileHelper::createFileFinder()
418
     */
419
    public static function findPHPFiles(string $targetFolder, array $options=array()) : array
420
    {
421
        return self::findFiles($targetFolder, array('php'), $options);
0 ignored issues
show
Deprecated Code introduced by
The function AppUtils\FileHelper::findFiles() has been deprecated: Use the file finder instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

421
        return /** @scrutinizer ignore-deprecated */ self::findFiles($targetFolder, array('php'), $options);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
422
    }
423
    
424
   /**
425
    * Finds files according to the specified options.
426
    * 
427
    * NOTE: This method only exists for backwards compatibility.
428
    * Use the {@see FileHelper::createFileFinder()} method instead,
429
    * which offers an object-oriented interface that is much easier
430
    * to use.
431
    *  
432
    * @param string|PathInfoInterface|DirectoryIterator $targetFolder
433
    * @param string[] $extensions
434
    * @param array<string,mixed> $options
435
    * @throws FileHelper_Exception
436
    * @return string[]
437
    *
438
    * @see FileHelper::createFileFinder()
439
    * @deprecated Use the file finder instead.
440
    */
441
    public static function findFiles($targetFolder, array $extensions=array(), array $options=array()) : array
442
    {
443
        $finder = self::createFileFinder($targetFolder);
444
445
        foreach ($extensions as $extension) {
446
            $finder->includeExtension($extension);
447
        }
448
449
        $finder->setPathmodeStrip();
450
        
451
        if(isset($options['relative-path']) && $options['relative-path'] === true) 
452
        {
453
            $finder->setPathmodeRelative();
454
        } 
455
        else if(isset($options['absolute-path']) && $options['absolute-path'] === true)
456
        {
457
            $finder->setPathmodeAbsolute();
458
        }
459
        
460
        if(isset($options['strip-extension'])) 
461
        {
462
            $finder->stripExtensions();
463
        }
464
        
465
        $finder->setOptions($options);
466
        
467
        return $finder->getAll();
468
    }
469
470
   /**
471
    * Removes the extension from the specified path or file name,
472
    * if any, and returns the name without the extension.
473
    * 
474
    * @param string $filename
475
    * @param bool $keepPath Whether to keep the path component, if any. Default PHP pathinfo behavior is not to.
476
    * @return string
477
    */
478
    public static function removeExtension(string $filename, bool $keepPath=false) : string
479
    {
480
        return self::getFileInfo($filename)->removeExtension($keepPath);
481
    }
482
483
    /**
484
     * @var UnicodeHandling|NULL
485
     */
486
    private static ?UnicodeHandling $unicodeHandling = null;
487
488
    public static function createUnicodeHandling() : UnicodeHandling
489
    {
490
        if(!isset(self::$unicodeHandling))
491
        {
492
            self::$unicodeHandling = new UnicodeHandling();
493
        }
494
495
        return self::$unicodeHandling;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::unicodeHandling could return the type null which is incompatible with the type-hinted return AppUtils\FileHelper\UnicodeHandling. Consider adding an additional type-check to rule them out.
Loading history...
496
    }
497
    
498
   /**
499
    * Normalizes the slash style in a file or folder path,
500
    * by replacing any anti-slashes with forward slashes.
501
    * 
502
    * @param string $path
503
    * @return string
504
    */
505
    public static function normalizePath(string $path) : string
506
    {
507
        return str_replace(array('\\', '//'), array('/', '/'), $path);
508
    }
509
    
510
   /**
511
    * Saves the specified data to a file, JSON encoded.
512
    * 
513
    * @param mixed $data
514
    * @param string $file
515
    * @param bool $pretty
516
    * @throws FileHelper_Exception
517
    * 
518
    * @see FileHelper::ERROR_JSON_ENCODE_ERROR
519
    * @see FileHelper::ERROR_SAVE_FOLDER_NOT_WRITABLE
520
    * @see FileHelper::ERROR_SAVE_FILE_NOT_WRITABLE
521
    * @see FileHelper::ERROR_SAVE_FILE_WRITE_FAILED
522
    */
523
    public static function saveAsJSON($data, string $file, bool $pretty=false) : void
524
    {
525
        JSONFile::factory(self::getFileInfo($file))
526
            ->putData($data, $pretty);
527
    }
528
   
529
   /**
530
    * Saves the specified content to the target file, creating
531
    * the file and the folder as necessary.
532
    * 
533
    * @param string $filePath
534
    * @param string $content
535
    * @throws FileHelper_Exception
536
    * 
537
    * @see FileHelper::ERROR_SAVE_FOLDER_NOT_WRITABLE
538
    * @see FileHelper::ERROR_SAVE_FILE_NOT_WRITABLE
539
    * @see FileHelper::ERROR_SAVE_FILE_WRITE_FAILED
540
    */
541
    public static function saveFile(string $filePath, string $content='') : void
542
    {
543
        self::getFileInfo($filePath)->putContents($content);
544
    }
545
546
    /**
547
     * Checks whether it is possible to run PHP command
548
     * line commands.
549
     *
550
     * @return boolean
551
     * @throws FileHelper_Exception
552
     */
553
    public static function canMakePHPCalls() : bool
554
    {
555
        return self::cliCommandExists('php');
556
    }
557
    
558
    /**
559
     * Determines if a command exists on the current environment's command line interface.
560
     *
561
     * @param string $command The name of the command to check, e.g. "php"
562
     * @return bool True if the command has been found, false otherwise.
563
     * @throws FileHelper_Exception
564
     * @see FileHelper::ERROR_UNSUPPORTED_OS_CLI_COMMAND
565
     */
566
    public static function cliCommandExists(string $command) : bool
567
    {
568
        return CLICommandChecker::factory()->exists($command);
569
    }
570
571
    /**
572
     * Validates a PHP file's syntax.
573
     *
574
     * NOTE: This will fail silently if the PHP command line
575
     * is not available. Use {@link FileHelper::canMakePHPCalls()}
576
     * to check this beforehand as needed.
577
     *
578
     * @param string $path
579
     * @return boolean|string[] A boolean true if the file is valid, an array with validation messages otherwise.
580
     * @throws FileHelper_Exception
581
     */
582
    public static function checkPHPFileSyntax(string $path)
583
    {
584
        if(!self::canMakePHPCalls()) {
585
            return true;
586
        }
587
        
588
        $output = array();
589
        $command = sprintf('php -l "%s" 2>&1', $path);
590
        exec($command, $output);
591
        
592
        // when the validation is successful, the first entry
593
        // in the array contains the success message. When it
594
        // is invalid, the first entry is always empty.
595
        if(!empty($output[0])) {
596
            return true;
597
        }
598
        
599
        array_shift($output); // the first entry is always empty
600
        array_pop($output); // the last message is a superfluous message saying there's an error
601
        
602
        return $output;
603
    }
604
    
605
   /**
606
    * Retrieves the last modified date for the specified file or folder.
607
    * 
608
    * Note: If the target does not exist, returns null. 
609
    * 
610
    * @param string $path
611
    * @return DateTime|NULL
612
    */
613
    public static function getModifiedDate(string $path) : ?DateTime
614
    {
615
        $time = filemtime($path);
616
        if($time === false) {
617
            return null;
618
        }
619
620
        $date = new DateTime();
621
        $date->setTimestamp($time);
622
        return $date;
623
    }
624
625
    /**
626
     * Retrieves the names of all sub-folders in the specified path.
627
     *
628
     * Available options:
629
     *
630
     * - recursive: true/false
631
     *   Whether to search for sub-folders recursively.
632
     *
633
     * - absolute-paths: true/false
634
     *   Whether to return a list of absolute paths.
635
     *
636
     * @param string|PathInfoInterface|DirectoryIterator $targetFolder
637
     * @param array<string,mixed> $options
638
     * @return string[]
639
     *
640
     * @throws FileHelper_Exception
641
     * @see FileHelper::ERROR_FIND_SUBFOLDERS_FOLDER_DOES_NOT_EXIST
642
     */
643
    public static function getSubfolders($targetFolder, array $options = array()) : array
644
    {
645
        return self::getPathInfo($targetFolder)
646
            ->requireIsFolder()
647
            ->createFolderFinder()
648
            ->setOptions($options)
649
            ->getPaths();
650
    }
651
652
   /**
653
    * Retrieves the maximum allowed upload file size, in bytes.
654
    * Takes into account the PHP ini settings <code>post_max_size</code>
655
    * and <code>upload_max_filesize</code>. Since these cannot
656
    * be modified at runtime, they are the hard limits for uploads.
657
    * 
658
    * NOTE: Based on binary values, where 1KB = 1024 Bytes.
659
    * 
660
    * @return int Will return <code>-1</code> if no limit.
661
    */
662
    public static function getMaxUploadFilesize() : int
663
    {
664
        return UploadFileSizeInfo::getFileSize();
665
    }
666
   
667
   /**
668
    * Makes a path relative using a folder depth: will reduce the
669
    * length of the path so that only the amount of folders defined
670
    * in the <code>$depth</code> attribute are shown below the actual
671
    * folder or file in the path.
672
    *  
673
    * @param string  $path The absolute or relative path
674
    * @param int $depth The folder depth to reduce the path to
675
    * @return string
676
    */
677
    public static function relativizePathByDepth(string $path, int $depth=2) : string
678
    {
679
        return PathRelativizer::relativizeByDepth($path, $depth);
680
    }
681
    
682
   /**
683
    * Makes the specified path relative to another path,
684
    * by removing one from the other if found. Also 
685
    * normalizes the path to use forward slashes. 
686
    * 
687
    * Example:
688
    * 
689
    * <pre>
690
    * relativizePath('c:\some\folder\to\file.txt', 'c:\some\folder');
691
    * </pre>
692
    * 
693
    * Result: <code>to/file.txt</code>
694
    * 
695
    * @param string $path
696
    * @param string $relativeTo
697
    * @return string
698
    */
699
    public static function relativizePath(string $path, string $relativeTo) : string
700
    {
701
        return PathRelativizer::relativize($path, $relativeTo);
702
    }
703
    
704
   /**
705
    * Checks that the target file exists, and throws an exception
706
    * if it does not. 
707
    * 
708
    * @param string|DirectoryIterator $path
709
    * @param int|NULL $errorCode Optional custom error code
710
    * @throws FileHelper_Exception
711
    * @return string The real path to the file
712
    * 
713
    * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
714
    * @see FileHelper::ERROR_REAL_PATH_NOT_FOUND
715
    */
716
    public static function requireFileExists($path, ?int $errorCode=null) : string
717
    {
718
        return self::getPathInfo($path)
719
            ->requireIsFile()
720
            ->requireExists($errorCode)
721
            ->getRealPath();
722
    }
723
724
    /**
725
     * @param string $path
726
     * @param int|NULL $errorCode
727
     * @return string
728
     * @throws FileHelper_Exception
729
     */
730
    public static function requireFileReadable(string $path, ?int $errorCode=null) : string
731
    {
732
        return self::getPathInfo($path)
733
            ->requireIsFile()
734
            ->requireReadable($errorCode)
735
            ->getPath();
736
    }
737
    
738
   /**
739
    * Reads a specific line number from the target file and returns its
740
    * contents, if the file has such a line. Does so with little memory
741
    * usage, as the file is not read entirely into memory.
742
    * 
743
    * @param string $path
744
    * @param int $lineNumber Note: 1-based; the first line is number 1.
745
    * @return string|NULL Will return null if the requested line does not exist.
746
    * @throws FileHelper_Exception
747
    * 
748
    * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
749
    */
750
    public static function getLineFromFile(string $path, int $lineNumber) : ?string
751
    {
752
        return self::getFileInfo($path)
753
            ->getLineReader()
754
            ->getLine($lineNumber);
755
    }
756
757
    /**
758
     * Retrieves the total amount of lines in the file, without
759
     * reading the whole file into memory.
760
     *
761
     * @param string $path
762
     * @return int
763
     * @throws FileHelper_Exception
764
     */
765
    public static function countFileLines(string $path) : int
766
    {
767
        return self::getFileInfo($path)
768
            ->getLineReader()
769
            ->countLines();
770
    }
771
772
    /**
773
     * Parses the target file to detect any PHP classes contained
774
     * within, and retrieve information on them. Does not use the
775
     * PHP reflection API.
776
     *
777
     * @param string $filePath
778
     * @return FileHelper_PHPClassInfo
779
     * @throws FileHelper_Exception
780
     */
781
    public static function findPHPClasses(string $filePath) : FileHelper_PHPClassInfo
782
    {
783
        return new FileHelper_PHPClassInfo($filePath);
784
    }
785
786
    /**
787
     * Detects the end of line style used in the target file, if any.
788
     * Can be used with large files, because it only reads part of it.
789
     *
790
     * @param string $filePath The path to the file.
791
     * @return NULL|ConvertHelper_EOL The end of line character information, or NULL if none is found.
792
     * @throws FileHelper_Exception
793
     */
794
    public static function detectEOLCharacter(string $filePath) : ?ConvertHelper_EOL
795
    {
796
        // 20 lines is enough to get a good picture of the newline style in the file.
797
        $amount = 20;
798
        
799
        $lines = self::readLines($filePath, $amount);
800
        
801
        $string = implode('', $lines);
802
        
803
        return ConvertHelper::detectEOLCharacter($string);
804
    }
805
806
    /**
807
     * Reads the specified amount of lines from the target file.
808
     * Unicode BOM compatible: any byte order marker is stripped
809
     * from the resulting lines.
810
     *
811
     * @param string $filePath
812
     * @param int $amount Set to 0 to read all lines.
813
     * @return string[]
814
     *
815
     * @throws FileHelper_Exception
816
     * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
817
     * @see FileHelper::ERROR_CANNOT_OPEN_FILE_TO_READ_LINES
818
     */
819
    public static function readLines(string $filePath, int $amount=0) : array
820
    {
821
        return self::getFileInfo($filePath)
822
            ->getLineReader()
823
            ->getLines($amount);
824
    }
825
    
826
   /**
827
    * Reads all content from a file.
828
    * 
829
    * @param string $filePath
830
    * @throws FileHelper_Exception
831
    * @return string
832
    * 
833
    * @see FileHelper::ERROR_FILE_DOES_NOT_EXIST
834
    * @see FileHelper::ERROR_CANNOT_READ_FILE_CONTENTS
835
    */
836
    public static function readContents(string $filePath) : string
837
    {
838
        return self::getFileInfo($filePath)->getContents();
839
    }
840
841
   /**
842
    * Ensures that the target path exists on disk, and is a folder.
843
    * 
844
    * @param string $path
845
    * @return string The real path, with normalized slashes.
846
    * @throws FileHelper_Exception
847
    * 
848
    * @see FileHelper::normalizePath()
849
    * 
850
    * @see FileHelper::ERROR_FOLDER_DOES_NOT_EXIST
851
    * @see FileHelper::ERROR_PATH_IS_NOT_A_FOLDER
852
    */
853
    public static function requireFolderExists(string $path) : string
854
    {
855
        return self::getFolderInfo($path)
856
            ->requireExists(self::ERROR_FOLDER_DOES_NOT_EXIST)
857
            ->getRealPath();
858
    }
859
860
    /**
861
     * Creates an instance of the path reducer tool, which can reduce
862
     * a list of paths to the closest common root folder.
863
     *
864
     * @param string[] $paths
865
     * @return PathsReducer
866
     *
867
     * @throws FileHelper_Exception
868
     */
869
    public static function createPathsReducer(array $paths=array()) : PathsReducer
870
    {
871
        return new PathsReducer($paths);
872
    }
873
}
874