Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
200 lars 6
 * (c) 2012-2023 Justin Hileman
148 lars 7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Psy;
13
 
14
/**
15
 * A Psy Shell configuration path helper.
16
 */
17
class ConfigPaths
18
{
19
    private $configDir;
20
    private $dataDir;
21
    private $runtimeDir;
22
    private $env;
23
 
24
    /**
25
     * ConfigPaths constructor.
26
     *
27
     * Optionally provide `configDir`, `dataDir` and `runtimeDir` overrides.
28
     *
29
     * @see self::overrideDirs
30
     *
31
     * @param string[]     $overrides Directory overrides
32
     * @param EnvInterface $env
33
     */
34
    public function __construct(array $overrides = [], EnvInterface $env = null)
35
    {
36
        $this->overrideDirs($overrides);
37
        $this->env = $env ?: new SuperglobalsEnv();
38
    }
39
 
40
    /**
41
     * Provide `configDir`, `dataDir` and `runtimeDir` overrides.
42
     *
43
     * If a key is set but empty, the override will be removed. If it is not set
44
     * at all, any existing override will persist.
45
     *
46
     * @param string[] $overrides Directory overrides
47
     */
48
    public function overrideDirs(array $overrides)
49
    {
50
        if (\array_key_exists('configDir', $overrides)) {
51
            $this->configDir = $overrides['configDir'] ?: null;
52
        }
53
 
54
        if (\array_key_exists('dataDir', $overrides)) {
55
            $this->dataDir = $overrides['dataDir'] ?: null;
56
        }
57
 
58
        if (\array_key_exists('runtimeDir', $overrides)) {
59
            $this->runtimeDir = $overrides['runtimeDir'] ?: null;
60
        }
61
    }
62
 
63
    /**
64
     * Get the current home directory.
65
     *
66
     * @return string|null
67
     */
68
    public function homeDir()
69
    {
70
        if ($homeDir = $this->getEnv('HOME') ?: $this->windowsHomeDir()) {
71
            return \strtr($homeDir, '\\', '/');
72
        }
73
 
74
        return null;
75
    }
76
 
77
    private function windowsHomeDir()
78
    {
79
        if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
80
            $homeDrive = $this->getEnv('HOMEDRIVE');
81
            $homePath = $this->getEnv('HOMEPATH');
82
            if ($homeDrive && $homePath) {
83
                return $homeDrive.'/'.$homePath;
84
            }
85
        }
86
 
87
        return null;
88
    }
89
 
90
    private function homeConfigDir()
91
    {
92
        if ($homeConfigDir = $this->getEnv('XDG_CONFIG_HOME')) {
93
            return $homeConfigDir;
94
        }
95
 
96
        $homeDir = $this->homeDir();
97
 
98
        return $homeDir === '/' ? $homeDir.'.config' : $homeDir.'/.config';
99
    }
100
 
101
    /**
102
     * Get potential config directory paths.
103
     *
104
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and all
105
     * XDG Base Directory config directories:
106
     *
107
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
108
     *
109
     * @return string[]
110
     */
111
    public function configDirs(): array
112
    {
113
        if ($this->configDir !== null) {
114
            return [$this->configDir];
115
        }
116
 
117
        $configDirs = $this->getEnvArray('XDG_CONFIG_DIRS') ?: ['/etc/xdg'];
118
 
119
        return $this->allDirNames(\array_merge([$this->homeConfigDir()], $configDirs));
120
    }
121
 
122
    /**
123
     * @deprecated
124
     */
125
    public static function getConfigDirs(): array
126
    {
127
        return (new self())->configDirs();
128
    }
129
 
130
    /**
131
     * Get potential home config directory paths.
132
     *
133
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the
134
     * XDG Base Directory home config directory:
135
     *
136
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
137
     *
138
     * @deprecated
139
     *
140
     * @return string[]
141
     */
142
    public static function getHomeConfigDirs(): array
143
    {
144
        // Not quite the same, but this is deprecated anyway /shrug
145
        return self::getConfigDirs();
146
    }
147
 
148
    /**
149
     * Get the current home config directory.
150
     *
151
     * Returns the highest precedence home config directory which actually
152
     * exists. If none of them exists, returns the highest precedence home
153
     * config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
154
     * everywhere else).
155
     *
156
     * @see self::homeConfigDir
157
     */
158
    public function currentConfigDir(): string
159
    {
160
        if ($this->configDir !== null) {
161
            return $this->configDir;
162
        }
163
 
164
        $configDirs = $this->allDirNames([$this->homeConfigDir()]);
165
 
166
        foreach ($configDirs as $configDir) {
167
            if (@\is_dir($configDir)) {
168
                return $configDir;
169
            }
170
        }
171
 
172
        return $configDirs[0];
173
    }
174
 
175
    /**
176
     * @deprecated
177
     */
178
    public static function getCurrentConfigDir(): string
179
    {
180
        return (new self())->currentConfigDir();
181
    }
182
 
183
    /**
184
     * Find real config files in config directories.
185
     *
186
     * @param string[] $names Config file names
187
     *
188
     * @return string[]
189
     */
190
    public function configFiles(array $names): array
191
    {
192
        return $this->allRealFiles($this->configDirs(), $names);
193
    }
194
 
195
    /**
196
     * @deprecated
197
     */
198
    public static function getConfigFiles(array $names, $configDir = null): array
199
    {
200
        return (new self(['configDir' => $configDir]))->configFiles($names);
201
    }
202
 
203
    /**
204
     * Get potential data directory paths.
205
     *
206
     * If a `dataDir` option was explicitly set, returns an array containing
207
     * just that directory.
208
     *
209
     * Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories:
210
     *
211
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
212
     *
213
     * @return string[]
214
     */
215
    public function dataDirs(): array
216
    {
217
        if ($this->dataDir !== null) {
218
            return [$this->dataDir];
219
        }
220
 
221
        $homeDataDir = $this->getEnv('XDG_DATA_HOME') ?: $this->homeDir().'/.local/share';
222
        $dataDirs = $this->getEnvArray('XDG_DATA_DIRS') ?: ['/usr/local/share', '/usr/share'];
223
 
224
        return $this->allDirNames(\array_merge([$homeDataDir], $dataDirs));
225
    }
226
 
227
    /**
228
     * @deprecated
229
     */
230
    public static function getDataDirs(): array
231
    {
232
        return (new self())->dataDirs();
233
    }
234
 
235
    /**
236
     * Find real data files in config directories.
237
     *
238
     * @param string[] $names Config file names
239
     *
240
     * @return string[]
241
     */
242
    public function dataFiles(array $names): array
243
    {
244
        return $this->allRealFiles($this->dataDirs(), $names);
245
    }
246
 
247
    /**
248
     * @deprecated
249
     */
250
    public static function getDataFiles(array $names, $dataDir = null): array
251
    {
252
        return (new self(['dataDir' => $dataDir]))->dataFiles($names);
253
    }
254
 
255
    /**
256
     * Get a runtime directory.
257
     *
258
     * Defaults to `/psysh` inside the system's temp dir.
259
     */
260
    public function runtimeDir(): string
261
    {
262
        if ($this->runtimeDir !== null) {
263
            return $this->runtimeDir;
264
        }
265
 
266
        // Fallback to a boring old folder in the system temp dir.
267
        $runtimeDir = $this->getEnv('XDG_RUNTIME_DIR') ?: \sys_get_temp_dir();
268
 
269
        return \strtr($runtimeDir, '\\', '/').'/psysh';
270
    }
271
 
272
    /**
273
     * @deprecated
274
     */
275
    public static function getRuntimeDir(): string
276
    {
277
        return (new self())->runtimeDir();
278
    }
279
 
280
    /**
281
     * Get a list of directories in PATH.
282
     *
283
     * If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
284
     *
285
     * @return string[]
286
     */
287
    public function pathDirs(): array
288
    {
289
        return $this->getEnvArray('PATH') ?: ['/usr/sbin', '/usr/bin', '/sbin', '/bin'];
290
    }
291
 
292
    /**
293
     * Locate a command (an executable) in $PATH.
294
     *
295
     * Behaves like 'command -v COMMAND' or 'which COMMAND'.
296
     * If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
297
     *
298
     * @param string $command the executable to locate
299
     *
300
     * @return string
301
     */
302
    public function which($command)
303
    {
304
        foreach ($this->pathDirs() as $path) {
305
            $fullpath = $path.\DIRECTORY_SEPARATOR.$command;
306
            if (@\is_file($fullpath) && @\is_executable($fullpath)) {
307
                return $fullpath;
308
            }
309
        }
310
 
311
        return null;
312
    }
313
 
314
    /**
315
     * Get all PsySH directory name candidates given a list of base directories.
316
     *
317
     * This expects that XDG-compatible directory paths will be passed in.
318
     * `psysh` will be added to each of $baseDirs, and we'll throw in `~/.psysh`
319
     * and a couple of Windows-friendly paths as well.
320
     *
321
     * @param string[] $baseDirs base directory paths
322
     *
323
     * @return string[]
324
     */
325
    private function allDirNames(array $baseDirs): array
326
    {
327
        $dirs = \array_map(function ($dir) {
328
            return \strtr($dir, '\\', '/').'/psysh';
329
        }, $baseDirs);
330
 
331
        // Add ~/.psysh
332
        if ($home = $this->getEnv('HOME')) {
333
            $dirs[] = \strtr($home, '\\', '/').'/.psysh';
334
        }
335
 
336
        // Add some Windows specific ones :)
337
        if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
338
            if ($appData = $this->getEnv('APPDATA')) {
339
                // AppData gets preference
340
                \array_unshift($dirs, \strtr($appData, '\\', '/').'/PsySH');
341
            }
342
 
343
            if ($windowsHomeDir = $this->windowsHomeDir()) {
344
                $dir = \strtr($windowsHomeDir, '\\', '/').'/.psysh';
345
                if (!\in_array($dir, $dirs)) {
346
                    $dirs[] = $dir;
347
                }
348
            }
349
        }
350
 
351
        return $dirs;
352
    }
353
 
354
    /**
355
     * Given a list of directories, and a list of filenames, find the ones that
356
     * are real files.
357
     *
358
     * @return string[]
359
     */
360
    private function allRealFiles(array $dirNames, array $fileNames): array
361
    {
362
        $files = [];
363
        foreach ($dirNames as $dir) {
364
            foreach ($fileNames as $name) {
365
                $file = $dir.'/'.$name;
366
                if (@\is_file($file)) {
367
                    $files[] = $file;
368
                }
369
            }
370
        }
371
 
372
        return $files;
373
    }
374
 
375
    /**
376
     * Ensure that $dir exists and is writable.
377
     *
378
     * Generates E_USER_NOTICE error if the directory is not writable or creatable.
379
     *
380
     * @param string $dir
381
     *
382
     * @return bool False if directory exists but is not writeable, or cannot be created
383
     */
384
    public static function ensureDir(string $dir): bool
385
    {
386
        if (!\is_dir($dir)) {
387
            // Just try making it and see if it works
388
            @\mkdir($dir, 0700, true);
389
        }
390
 
391
        if (!\is_dir($dir) || !\is_writable($dir)) {
392
            \trigger_error(\sprintf('Writing to directory %s is not allowed.', $dir), \E_USER_NOTICE);
393
 
394
            return false;
395
        }
396
 
397
        return true;
398
    }
399
 
400
    /**
401
     * Ensure that $file exists and is writable, make the parent directory if necessary.
402
     *
403
     * Generates E_USER_NOTICE error if either $file or its directory is not writable.
404
     *
405
     * @param string $file
406
     *
407
     * @return string|false Full path to $file, or false if file is not writable
408
     */
409
    public static function touchFileWithMkdir(string $file)
410
    {
411
        if (\file_exists($file)) {
412
            if (\is_writable($file)) {
413
                return $file;
414
            }
415
 
416
            \trigger_error(\sprintf('Writing to %s is not allowed.', $file), \E_USER_NOTICE);
417
 
418
            return false;
419
        }
420
 
421
        if (!self::ensureDir(\dirname($file))) {
422
            return false;
423
        }
424
 
425
        \touch($file);
426
 
427
        return $file;
428
    }
429
 
430
    private function getEnv($key)
431
    {
432
        return $this->env->get($key);
433
    }
434
 
435
    private function getEnvArray($key)
436
    {
437
        if ($value = $this->getEnv($key)) {
438
            return \explode(\PATH_SEPARATOR, $value);
439
        }
440
 
441
        return null;
442
    }
443
}