[BUGFIX] Merge t3lib_exec class with t3lib_utility_Command
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / CommandUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2011 Steffen Kamper <steffen@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Class to handle system commands.
31 * finds executables (programs) on Unix and Windows without knowing where they are
32 *
33 * returns exec command for a program
34 * or FALSE
35 *
36 * This class is meant to be used without instance:
37 * $cmd = CommandUtility::getCommand ('awstats','perl');
38 *
39 * The data of this class is cached.
40 * That means if a program is found once it don't have to be searched again.
41 *
42 * user functions:
43 *
44 * addPaths() could be used to extend the search paths
45 * getCommand() get a command string
46 * checkCommand() returns TRUE if a command is available
47 *
48 * Search paths that are included:
49 * $TYPO3_CONF_VARS['GFX']['im_path_lzw'] or $TYPO3_CONF_VARS['GFX']['im_path']
50 * $TYPO3_CONF_VARS['SYS']['binPath']
51 * $GLOBALS['_SERVER']['PATH']
52 * '/usr/bin/,/usr/local/bin/' on Unix
53 *
54 * binaries can be preconfigured with
55 * $TYPO3_CONF_VARS['SYS']['binSetup']
56 *
57 * @author Steffen Kamper <steffen@typo3.org>
58 * @author René Fritz <r.fritz@colorcube.de>
59 */
60 final class CommandUtility {
61
62 /**
63 * Tells if object is already initialized
64 *
65 * @var boolean
66 */
67 protected static $initialized = FALSE;
68
69 /**
70 * Contains application list. This is an array with the following structure:
71 * - app => file name to the application (like 'tar' or 'bzip2')
72 * - path => full path to the application without application name (like '/usr/bin/' for '/usr/bin/tar')
73 * - valid => TRUE or FALSE
74 * Array key is identical to 'app'.
75 *
76 * @var array
77 */
78 protected static $applications = array();
79
80 /**
81 * Paths where to search for applications
82 *
83 * @var array
84 */
85 protected static $paths = NULL;
86
87 /**
88 * Wrapper function for php exec function
89 * Needs to be central to have better control and possible fix for issues
90 *
91 * @static
92 * @param string $command
93 * @param NULL|array $output
94 * @param integer $returnValue
95 * @return NULL|array
96 */
97 static public function exec($command, &$output = NULL, &$returnValue = 0) {
98 $lastLine = exec($command, $output, $returnValue);
99 return $lastLine;
100 }
101
102 /**
103 * Compile the command for running ImageMagick/GraphicsMagick.
104 *
105 * @param string $command Command to be run: identify, convert or combine/composite
106 * @param string $parameters The parameters string
107 * @param string $path Override the default path (e.g. used by the install tool)
108 * @return string Compiled command that deals with IM6 & GraphicsMagick
109 */
110 static public function imageMagickCommand($command, $parameters, $path = '') {
111 $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
112 $isExt = TYPO3_OS == 'WIN' ? '.exe' : '';
113 $switchCompositeParameters = FALSE;
114 if (!$path) {
115 $path = $gfxConf['im_path'];
116 }
117 $path = \TYPO3\CMS\Core\Utility\GeneralUtility::fixWindowsFilePath($path);
118 $im_version = strtolower($gfxConf['im_version_5']);
119 $combineScript = $gfxConf['im_combine_filename'] ? trim($gfxConf['im_combine_filename']) : 'combine';
120 // This is only used internally, has no effect outside
121 if ($command === 'combine') {
122 $command = 'composite';
123 }
124 // Compile the path & command
125 if ($im_version === 'gm') {
126 $switchCompositeParameters = TRUE;
127 $path = (escapeshellarg((($path . 'gm') . $isExt)) . ' ') . $command;
128 } else {
129 if ($im_version === 'im6') {
130 $switchCompositeParameters = TRUE;
131 }
132 $path = escapeshellarg(($path . ($command == 'composite' ? $combineScript : $command)) . $isExt);
133 }
134 // strip profile information for thumbnails and reduce their size
135 if ((($parameters && $command != 'identify') && $gfxConf['im_useStripProfileByDefault']) && $gfxConf['im_stripProfileCommand'] != '') {
136 if (strpos($parameters, $gfxConf['im_stripProfileCommand']) === FALSE) {
137 // Determine whether the strip profile action has be disabled by TypoScript:
138 if ($parameters !== '-version' && strpos($parameters, '###SkipStripProfile###') === FALSE) {
139 $parameters = ($gfxConf['im_stripProfileCommand'] . ' ') . $parameters;
140 } else {
141 $parameters = str_replace('###SkipStripProfile###', '', $parameters);
142 }
143 }
144 }
145 $cmdLine = ($path . ' ') . $parameters;
146 // Because of some weird incompatibilities between ImageMagick 4 and 6 (plus GraphicsMagick),
147 // it is needed to change the parameters order under some preconditions
148 if ($command == 'composite' && $switchCompositeParameters) {
149 $paramsArr = \TYPO3\CMS\Core\Utility\GeneralUtility::unQuoteFilenames($parameters);
150 // The mask image has been specified => swap the parameters
151 if (count($paramsArr) > 5) {
152 $tmp = $paramsArr[count($paramsArr) - 3];
153 $paramsArr[count($paramsArr) - 3] = $paramsArr[count($paramsArr) - 4];
154 $paramsArr[count($paramsArr) - 4] = $tmp;
155 }
156 $cmdLine = ($path . ' ') . implode(' ', $paramsArr);
157 }
158 return $cmdLine;
159 }
160
161 /**
162 * Checks if a command is valid or not, updates global variables
163 *
164 * @param string $cmd The command that should be executed. eg: "convert"
165 * @param string $handler Executer for the command. eg: "perl"
166 * @return boolean FALSE if cmd is not found, or -1 if the handler is not found
167 */
168 public static function checkCommand($cmd, $handler = '') {
169 if (!self::init()) {
170 return FALSE;
171 }
172
173 if ($handler && !self::checkCommand($handler)) {
174 return -1;
175 }
176 // Already checked and valid
177 if (self::$applications[$cmd]['valid']) {
178 return TRUE;
179 }
180 // Is set but was (above) not TRUE
181 if (isset(self::$applications[$cmd]['valid'])) {
182 return FALSE;
183 }
184
185 foreach (self::$paths as $path => $validPath) {
186 // Ignore invalid (FALSE) paths
187 if ($validPath) {
188 if (TYPO3_OS == 'WIN') {
189 // Windows OS
190 // TODO Why is_executable() is not called here?
191 if (@is_file($path . $cmd)) {
192 self::$applications[$cmd]['app'] = $cmd;
193 self::$applications[$cmd]['path'] = $path;
194 self::$applications[$cmd]['valid'] = TRUE;
195 return TRUE;
196 }
197 if (@is_file($path . $cmd . '.exe')) {
198 self::$applications[$cmd]['app'] = $cmd . '.exe';
199 self::$applications[$cmd]['path'] = $path;
200 self::$applications[$cmd]['valid'] = TRUE;
201 return TRUE;
202 }
203 } else {
204 // Unix-like OS
205 $filePath = realpath($path . $cmd);
206 if ($filePath && @is_executable($filePath)) {
207 self::$applications[$cmd]['app'] = $cmd;
208 self::$applications[$cmd]['path'] = $path;
209 self::$applications[$cmd]['valid'] = TRUE;
210 return TRUE;
211 }
212 }
213 }
214 }
215
216 // Try to get the executable with the command 'which'.
217 // It does the same like already done, but maybe on other paths
218 if (TYPO3_OS != 'WIN') {
219 $cmd = @self::exec('which ' . $cmd);
220 if (@is_executable($cmd)) {
221 self::$applications[$cmd]['app'] = $cmd;
222 self::$applications[$cmd]['path'] = dirname($cmd) . '/';
223 self::$applications[$cmd]['valid'] = TRUE;
224 return TRUE;
225 }
226 }
227
228 return FALSE;
229 }
230
231 /**
232 * Returns a command string for exec(), system()
233 *
234 * @param string $cmd The command that should be executed. eg: "convert"
235 * @param string $handler Handler (executor) for the command. eg: "perl"
236 * @param string $handlerOpt Options for the handler, like '-w' for "perl"
237 * @return mixed Returns command string, or FALSE if cmd is not found, or -1 if the handler is not found
238 */
239 public static function getCommand($cmd, $handler = '', $handlerOpt = '') {
240 if (!self::init()) {
241 return FALSE;
242 }
243
244 // Handler
245 if ($handler) {
246 $handler = self::getCommand($handler);
247
248 if (!$handler) {
249 return -1;
250 }
251 $handler .= ' ' . $handlerOpt . ' ';
252 }
253
254 // Command
255 if (!self::checkCommand($cmd)) {
256 return FALSE;
257 }
258 $cmd = self::$applications[$cmd]['path'] . self::$applications[$cmd]['app'] . ' ';
259
260 return trim($handler . $cmd);
261 }
262
263 /**
264 * Extend the preset paths. This way an extension can install an executable and provide the path to t3lib_exec.
265 *
266 * @param string $paths Comma separated list of extra paths where a command should be searched. Relative paths (without leading "/") are prepend with site root path (PATH_site).
267 * @return void
268 */
269 public static function addPaths($paths) {
270 self::initPaths($paths);
271 }
272
273 /**
274 * Returns an array of search paths
275 *
276 * @param boolean $addInvalid If set the array contains invalid path too. Then the key is the path and the value is empty
277 * @return array Array of search paths (empty if exec is disabled)
278 */
279 public static function getPaths($addInvalid = FALSE) {
280 if (!self::init()) {
281 return array();
282 }
283
284 $paths = self::$paths;
285
286 if (!$addInvalid) {
287 foreach ($paths as $path => $validPath) {
288 if (!$validPath) {
289 unset($paths[$path]);
290 }
291 }
292 }
293 return $paths;
294 }
295
296 /**
297 * Initializes this class
298 *
299 * @return boolean
300 */
301 protected static function init() {
302 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['disable_exec_function']) {
303 return FALSE;
304 }
305 if (!self::$initialized) {
306 self::initPaths();
307 self::$applications = self::getConfiguredApps();
308 self::$initialized = TRUE;
309 }
310 return TRUE;
311 }
312
313 /**
314 * Initializes and extends the preset paths with own
315 *
316 * @param string $paths Comma separated list of extra paths where a command should be searched. Relative paths (without leading "/") are prepend with site root path (PATH_site).
317 * @return void
318 */
319 protected static function initPaths($paths = '') {
320 $doCheck = FALSE;
321
322 // Init global paths array if not already done
323 if (!is_array(self::$paths)) {
324 self::$paths = self::getPathsInternal();
325 $doCheck = TRUE;
326 }
327 // Merge the submitted paths array to the global
328 if ($paths) {
329 $paths = GeneralUtility::trimExplode(',', $paths, 1);
330 if (is_array($paths)) {
331 foreach ($paths as $path) {
332 // Make absolute path of relative
333 if (!preg_match('#^/#', $path)) {
334 $path = PATH_site . $path;
335 }
336 if (!isset(self::$paths[$path])) {
337 if (@is_dir($path)) {
338 self::$paths[$path] = $path;
339 } else {
340 self::$paths[$path] = FALSE;
341 }
342 }
343 }
344 }
345 }
346 // Check if new paths are invalid
347 if ($doCheck) {
348 foreach (self::$paths as $path => $valid) {
349 // Ignore invalid (FALSE) paths
350 if ($valid AND !@is_dir($path)) {
351 self::$paths[$path] = FALSE;
352 }
353 }
354 }
355 }
356
357 /**
358 * Processes and returns the paths from $GLOBALS['TYPO3_CONF_VARS']['SYS']['binSetup']
359 *
360 * @return array Array of commands and path
361 */
362 protected static function getConfiguredApps() {
363 $cmdArr = array();
364
365 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['binSetup']) {
366 $pathSetup = preg_split('/[\n,]+/', $GLOBALS['TYPO3_CONF_VARS']['SYS']['binSetup']);
367 foreach ($pathSetup as $val) {
368 list($cmd, $cmdPath) = GeneralUtility::trimExplode('=', $val, 1);
369 $cmdArr[$cmd]['app'] = basename($cmdPath);
370 $cmdArr[$cmd]['path'] = dirname($cmdPath) . '/';
371 $cmdArr[$cmd]['valid'] = TRUE;
372 }
373 }
374
375 return $cmdArr;
376 }
377
378 /**
379 * Sets the search paths from different sources, internal
380 *
381 * @return array Array of absolute paths (keys and values are equal)
382 */
383 protected static function getPathsInternal() {
384
385 $pathsArr = array();
386 $sysPathArr = array();
387
388 // Image magick paths first
389 // im_path_lzw take precedence over im_path
390 if (($imPath = ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw'] ? $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path_lzw'] : $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_path']))) {
391 $imPath = self::fixPath($imPath);
392 $pathsArr[$imPath] = $imPath;
393 }
394
395 // Add configured paths
396 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['binPath']) {
397 $sysPath = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['SYS']['binPath'], 1);
398 foreach ($sysPath as $val) {
399 $val = self::fixPath($val);
400 $sysPathArr[$val] = $val;
401 }
402 }
403
404 // Add path from environment
405 // TODO: how does this work for WIN
406 if ($GLOBALS['_SERVER']['PATH']) {
407 $sep = (TYPO3_OS == 'WIN' ? ';' : ':');
408 $envPath = GeneralUtility::trimExplode($sep, $GLOBALS['_SERVER']['PATH'], 1);
409 foreach ($envPath as $val) {
410 $val = self::fixPath($val);
411 $sysPathArr[$val] = $val;
412 }
413 }
414
415 // Set common paths for Unix (only)
416 if (TYPO3_OS !== 'WIN') {
417 $sysPathArr = array_merge($sysPathArr, array(
418 '/usr/bin/' => '/usr/bin/',
419 '/usr/local/bin/' => '/usr/local/bin/',
420 ));
421 }
422
423 $pathsArr = array_merge($pathsArr, $sysPathArr);
424
425 return $pathsArr;
426 }
427
428
429 /**
430 * Set a path to the right format
431 *
432 * @param string $path Input path
433 * @return string Output path
434 */
435 protected static function fixPath($path) {
436 return str_replace('//', '/', $path . '/');
437 }
438
439 }
440
441
442 ?>