Main::hookModuleApiProcess()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 3
1
<?php
2
3
/**
4
 * @package CLI API
5
 * @author Iurii Makukh <[email protected]>
6
 * @copyright Copyright (c) 2018, Iurii Makukh <[email protected]>
7
 * @license https://www.gnu.org/licenses/gpl-3.0.en.html GPL-3.0+
8
 */
9
10
namespace gplcart\modules\cli_api;
11
12
use Exception;
13
use gplcart\core\Config;
14
use gplcart\core\Container;
15
use RuntimeException;
16
use UnexpectedValueException;
17
18
/**
19
 * Main class for CLI API module
20
 */
21
class Main
22
{
23
24
    /**
25
     * Config class instance
26
     * @var \gplcart\core\Config $config
27
     */
28
    protected $config;
29
30
    /**
31
     * @param Config $config
32
     */
33
    public function __construct(Config $config)
34
    {
35
        $this->config = $config;
36
    }
37
38
    /**
39
     * Implements hook "module.install.before"
40
     * @param null|string $result
41
     */
42
    public function hookModuleInstallBefore(&$result)
43
    {
44
        $exec_enabled = function_exists('exec')
45
            && !in_array('exec', array_map('trim', explode(',', ini_get('disable_functions'))))
46
            && exec('echo EXEC') === 'EXEC';
47
48
        if (!$exec_enabled) {
49
            $result = gplcart_text('exec() function is disabled');
50
        }
51
    }
52
53
    /**
54
     * Implements hook "module.uninstall.before"
55
     */
56
    public function hookModuleUninstallAfter()
57
    {
58
        $this->config->reset('module_cli_api_phpexe_file');
59
    }
60
61
    /**
62
     * Implements hook "module.api.process"
63
     * @param array $params
64
     * @param array $user
65
     * @param mixed $response
66
     */
67
    public function hookModuleApiProcess(array $params, array $user, &$response)
68
    {
69
        if (!isset($response)) {
70
71
            $result = $this->exec($params, $user);
72
            $response = json_decode($result, true);
73
74
            if (json_last_error() !== JSON_ERROR_NONE) {
75
                $response = $result;
76
            }
77
        }
78
    }
79
80
    /**
81
     * Executes a command using an array of API request parameters
82
     * @param array $params
83
     * @param array $user
84
     * @param bool $return_string
85
     * @return string|array
86
     */
87
    public function exec(array $params, array $user, $return_string = true)
88
    {
89
        try {
90
            $php = $this->getPhpExe();
91
        } catch (Exception $ex) {
92
            return 'Error defining PHP executable: ' . $ex->getMessage();
93
        }
94
95
        try {
96
            $cmd = $this->getCommand($params, $user);
97
        } catch (Exception $ex) {
98
            return 'Error constructing CLI command: ' . $ex->getMessage();
99
        }
100
101
        $exe = GC_FILE_CLI;
102
        $output = $return = null;
103
104
        exec(escapeshellcmd("$php $exe $cmd"), $output, $return);
105
106
        if (!empty($return) && empty($output)) {
107
            $output = $return;
108
        }
109
110
        return $return_string ? trim(implode('', $output)) : $output;
111
    }
112
113
    /**
114
     * Returns a saved path to PHP executable file
115
     * @return string
116
     */
117
    protected function getPhpExe()
118
    {
119
        $file = (string) $this->config->get('module_cli_api_phpexe_file', '');
120
121
        if (empty($file)) {
122
            $file = $this->findPhpExe();
123
            $this->setPhpExe($file);
124
        }
125
126
        return $file;
127
    }
128
129
    /**
130
     * Sets the path to PHP executable file
131
     * @param string $file
132
     * @return bool
133
     */
134
    public function setPhpExe($file)
135
    {
136
        return $this->config->set('module_cli_api_phpexe_file', $file);
137
    }
138
139
    /**
140
     * Returns the path to PHP executable file
141
     * @return string
142
     * @throws RuntimeException
143
     */
144
    public function findPhpExe()
145
    {
146
        $php = getenv('PHP_BINARY');
147
148
        if (!empty($php)) {
149
150
            if (is_executable($php)) {
151
                return $php;
152
            }
153
154
            throw new RuntimeException('PHP_BINARY is not executable');
155
        }
156
157
        $php = getenv('PHP_PATH');
158
159
        if (!empty($php)) {
160
161
            if (is_executable($php)) {
162
                return $php;
163
            }
164
165
            throw new RuntimeException('PHP_PATH is not executable');
166
        }
167
168
        $php = getenv('PHP_PEAR_PHP_BIN');
169
170
        if (!empty($php) && is_executable($php)) {
171
            return $php;
172
        }
173
174
        $php = PHP_BINDIR . (DIRECTORY_SEPARATOR === '\\' ? '\\php.exe' : '/php');
175
176
        if (is_executable($php)) {
177
            return $php;
178
        }
179
180
        if (ini_get('open_basedir')) {
181
182
            $dirs = array();
183
            foreach (explode(PATH_SEPARATOR, ini_get('open_basedir')) as $path) {
184
185
                // Silencing against https://bugs.php.net/69240
186
                if (@is_dir($path)) {
187
                    $dirs[] = $path;
188
                    continue;
189
                }
190
191
                if (basename($path) === 'php' && @is_executable($path)) {
192
                    return $path;
193
                }
194
            }
195
196
        } else {
197
198
            $dirs = array(PHP_BINDIR);
199
200
            if (DIRECTORY_SEPARATOR === '\\') {
201
                $dirs[] = 'C:\xampp\php\\';
202
            }
203
204
            $dirs = array_merge(explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), $dirs);
205
        }
206
207
        $suffixes = array('');
208
209
        if (DIRECTORY_SEPARATOR === '\\') {
210
            $path_ext = getenv('PATHEXT');
211
            if (!empty($path_ext)) {
212
                $suffixes = array_merge($suffixes, explode(PATH_SEPARATOR, $path_ext));
213
            }
214
        }
215
216
        foreach ($suffixes as $suffix) {
217
            foreach ($dirs as $dir) {
218
                $file = $dir . DIRECTORY_SEPARATOR . "php$suffix";
219
                if (@is_file($file) && (DIRECTORY_SEPARATOR === '\\' || is_executable($file))) {
220
                    return $file;
221
                }
222
            }
223
        }
224
225
        throw new RuntimeException('Cannot find PHP executable file');
226
    }
227
228
    /**
229
     * Convert an array of params into a CLI command
230
     * @param array $params
231
     * @param array $user
232
     * @throws RuntimeException
233
     * @throws UnexpectedValueException
234
     * @return string
235
     */
236
    public function getCommand(array $params, array $user)
237
    {
238
        $params += array(
239
            'get' => array(),
240
            'post' => array(),
241
            'arguments' => array()
242
        );
243
244
        if (count($params['arguments']) != 1) {
245
            throw new UnexpectedValueException('"arguments" key must contain exactly one array element');
246
        }
247
248
        if (empty($user['user_id'])) {
249
            throw new UnexpectedValueException('Second argument must contain a valid user ID under "user_id" key');
250
        }
251
252
        $command = array(reset($params['arguments']));
253
254
        $route = $this->getCliRouteInstance()->get($command[0]);
255
256
        if (empty($route['access'])) {
257
            throw new RuntimeException('Undefined user access');
258
        }
259
260
        foreach (array_merge($params['get'], $params['post']) as $key => $value) {
261
            if (is_string($value) && strlen($key) > 1) {
262
                $command[] = rtrim("--$key=" . escapeshellarg($value), '=');
263
            }
264
        }
265
266
        $command[] = " -u=" . escapeshellarg($user['user_id']);
267
        $command[] = " -f=json";
268
269
        return implode(' ', $command);
270
    }
271
272
    /**
273
     * Returns CLI router instance
274
     * @return \gplcart\core\CliRoute
275
     */
276
    protected function getCliRouteInstance()
277
    {
278
        /** @var \gplcart\core\CliRoute $instance */
279
        $instance = Container::get('gplcart\\core\\CliRoute');
280
        return $instance;
281
    }
282
}
283