Completed
Pull Request — develop (#362)
by Stephan
07:42
created

WindowsSystem::locateProgram()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 33
rs 5.3846
c 1
b 0
f 0
cc 8
eloc 18
nc 7
nop 1
1
<?php
2
/*
3
 * @author Tom Klingenberg <[email protected]>
4
 */
5
6
namespace N98\Util;
7
8
/**
9
 * Class WindowsSystem
10
 *
11
 * Utility class with global static functions.
12
 *
13
 * @package N98\Util
14
 */
15
class WindowsSystem
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
16
{
17
    const PATH_SEPARATOR = ';';
18
19
    const FORBIDDEN_CHARS = '<>:"/\|?*';
20
21
    /**
22
     * @var WindowsSystem
23
     */
24
    private static $instance;
25
26
    /**
27
     * @var array
28
     */
29
    private $exts;
30
31
    /**
32
     * an instance is bootstrapped in to prevent initialization overhead
33
     *
34
     * @return WindowsSystem
35
     */
36
    private static function getInstance()
37
    {
38
        self::$instance || self::$instance = new WindowsSystem();
39
40
        return self::$instance;
41
    }
42
43
    private function __construct()
44
    {
45
    }
46
47
    /**
48
     * @return array keys are uppercase extensions incl. dot
49
     */
50
    private function getExecuteableExtesions()
51
    {
52
        // PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.PSC1
53
        $this->exts || $this->exts = array_flip(array_map(
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->exts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
54
            'strtoupper',
55
            explode(self::PATH_SEPARATOR, getenv('PATHEXT'))
56
        ));
57
58
        return $this->exts;
59
    }
60
61
    /**
62
     * a name is executable based on it's extension
63
     *
64
     * @param string $name
65
     *
66
     * @return bool
67
     */
68
    public static function isExecutableName($name)
69
    {
70
        // invalid name is never executable
71
        if (false !== strpbrk($name, self::FORBIDDEN_CHARS)) {
72
            return false;
73
        }
74
75
        $compare = '.' . strtoupper(pathinfo($name, PATHINFO_EXTENSION));
76
77
        if ($compare === '.') {
78
            return false;
79
        }
80
81
        $exts = self::getInstance()->getExecuteableExtesions();
82
83
        return isset($exts[$compare]);
84
    }
85
86
    /**
87
     * a program (by it's basename) is available on system for execution
88
     *
89
     * @param string $program
90
     * @return bool
91
     */
92
    public static function isProgramInstalled($program)
93
    {
94
        return '' !== self::locateProgram($program);
95
    }
96
97
    /**
98
     * Returns the absolute path to the program that should be located or an empty string if the programm
99
     * could not be found.
100
     *
101
     * @param string $program
102
     * @return string
103
     */
104
    public static function locateProgram($program)
105
    {
106
        // programs with an invalid name do not exist
107
        if (false !== strpbrk($program, self::FORBIDDEN_CHARS)) {
108
            return '';
109
        }
110
111
        $isExecutable = self::isExecutableName($program);
112
113
        $paths = explode(self::PATH_SEPARATOR, getenv('PATH'));
114
        array_unshift($paths, getcwd());
115
        $exts = self::getInstance()->getExecuteableExtesions();
116
117
        foreach ($paths as $path) {
118
            if (!is_dir($path)) {
119
                continue;
120
            }
121
            $file = $path . '/' . $program;
122
123
            if ($isExecutable && is_readable($file)) {
124
                return $file;
125
            }
126
127
            foreach ($exts as $ext => $index) {
128
                $fileEx = $file . $ext;
129
                if (is_readable($fileEx)) {
130
                    return $fileEx;
131
                }
132
            }
133
        }
134
135
        return '';
136
    }
137
}
138