diff options
Diffstat (limited to 'application/helper/FileUtils.php')
-rw-r--r-- | application/helper/FileUtils.php | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/application/helper/FileUtils.php b/application/helper/FileUtils.php new file mode 100644 index 00000000..2d50d850 --- /dev/null +++ b/application/helper/FileUtils.php | |||
@@ -0,0 +1,140 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Helper; | ||
4 | |||
5 | use Shaarli\Exceptions\IOException; | ||
6 | |||
7 | /** | ||
8 | * Class FileUtils | ||
9 | * | ||
10 | * Utility class for file manipulation. | ||
11 | */ | ||
12 | class FileUtils | ||
13 | { | ||
14 | /** | ||
15 | * @var string | ||
16 | */ | ||
17 | protected static $phpPrefix = '<?php /* '; | ||
18 | |||
19 | /** | ||
20 | * @var string | ||
21 | */ | ||
22 | protected static $phpSuffix = ' */ ?>'; | ||
23 | |||
24 | /** | ||
25 | * Write data into a file (Shaarli database format). | ||
26 | * The data is stored in a PHP file, as a comment, in compressed base64 format. | ||
27 | * | ||
28 | * The file will be created if it doesn't exist. | ||
29 | * | ||
30 | * @param string $file File path. | ||
31 | * @param mixed $content Content to write. | ||
32 | * | ||
33 | * @return int|bool Number of bytes written or false if it fails. | ||
34 | * | ||
35 | * @throws IOException The destination file can't be written. | ||
36 | */ | ||
37 | public static function writeFlatDB($file, $content) | ||
38 | { | ||
39 | if (is_file($file) && !is_writeable($file)) { | ||
40 | // The datastore exists but is not writeable | ||
41 | throw new IOException($file); | ||
42 | } elseif (!is_file($file) && !is_writeable(dirname($file))) { | ||
43 | // The datastore does not exist and its parent directory is not writeable | ||
44 | throw new IOException(dirname($file)); | ||
45 | } | ||
46 | |||
47 | return file_put_contents( | ||
48 | $file, | ||
49 | self::$phpPrefix . base64_encode(gzdeflate(serialize($content))) . self::$phpSuffix | ||
50 | ); | ||
51 | } | ||
52 | |||
53 | /** | ||
54 | * Read data from a file containing Shaarli database format content. | ||
55 | * | ||
56 | * If the file isn't readable or doesn't exist, default data will be returned. | ||
57 | * | ||
58 | * @param string $file File path. | ||
59 | * @param mixed $default The default value to return if the file isn't readable. | ||
60 | * | ||
61 | * @return mixed The content unserialized, or default if the file isn't readable, or false if it fails. | ||
62 | */ | ||
63 | public static function readFlatDB($file, $default = null) | ||
64 | { | ||
65 | // Note that gzinflate is faster than gzuncompress. | ||
66 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
67 | if (!is_readable($file)) { | ||
68 | return $default; | ||
69 | } | ||
70 | |||
71 | $data = file_get_contents($file); | ||
72 | if ($data == '') { | ||
73 | return $default; | ||
74 | } | ||
75 | |||
76 | return unserialize( | ||
77 | gzinflate( | ||
78 | base64_decode( | ||
79 | substr($data, strlen(self::$phpPrefix), -strlen(self::$phpSuffix)) | ||
80 | ) | ||
81 | ) | ||
82 | ); | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Recursively deletes a folder content, and deletes itself optionally. | ||
87 | * If an excluded file is found, folders won't be deleted. | ||
88 | * | ||
89 | * Additional security: raise an exception if it tries to delete a folder outside of Shaarli directory. | ||
90 | * | ||
91 | * @param string $path | ||
92 | * @param bool $selfDelete Delete the provided folder if true, only its content if false. | ||
93 | * @param array $exclude | ||
94 | */ | ||
95 | public static function clearFolder(string $path, bool $selfDelete, array $exclude = []): bool | ||
96 | { | ||
97 | $skipped = false; | ||
98 | |||
99 | if (!is_dir($path)) { | ||
100 | throw new IOException(t('Provided path is not a directory.')); | ||
101 | } | ||
102 | |||
103 | if (!static::isPathInShaarliFolder($path)) { | ||
104 | throw new IOException(t('Trying to delete a folder outside of Shaarli path.')); | ||
105 | } | ||
106 | |||
107 | foreach (new \DirectoryIterator($path) as $file) { | ||
108 | if($file->isDot()) { | ||
109 | continue; | ||
110 | } | ||
111 | |||
112 | if (in_array($file->getBasename(), $exclude, true)) { | ||
113 | $skipped = true; | ||
114 | continue; | ||
115 | } | ||
116 | |||
117 | if ($file->isFile()) { | ||
118 | unlink($file->getPathname()); | ||
119 | } elseif($file->isDir()) { | ||
120 | $skipped = static::clearFolder($file->getRealPath(), true, $exclude) || $skipped; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | if ($selfDelete && !$skipped) { | ||
125 | rmdir($path); | ||
126 | } | ||
127 | |||
128 | return $skipped; | ||
129 | } | ||
130 | |||
131 | /** | ||
132 | * Checks that the given path is inside Shaarli directory. | ||
133 | */ | ||
134 | public static function isPathInShaarliFolder(string $path): bool | ||
135 | { | ||
136 | $rootDirectory = dirname(dirname(__FILE__)); | ||
137 | |||
138 | return strpos(realpath($path), $rootDirectory) !== false; | ||
139 | } | ||
140 | } | ||