[TASK] Deprecate T3_ERR_SV_ constants
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Service / AbstractService.php
1 <?php
2 namespace TYPO3\CMS\Core\Service;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
20 use TYPO3\CMS\Core\Utility\CommandUtility;
21 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Parent class for "Services" classes
26 */
27 abstract class AbstractService implements LoggerAwareInterface
28 {
29 use LoggerAwareTrait;
30
31 // General error - something went wrong
32 const ERROR_GENERAL = -1;
33
34 // During execution it showed that the service is not available and
35 // should be ignored. The service itself should call $this->setNonAvailable()
36 const ERROR_SERVICE_NOT_AVAILABLE = -2;
37
38 // Passed subtype is not possible with this service
39 const ERROR_WRONG_SUBTYPE = -3;
40
41 // Passed subtype is not possible with this service
42 const ERROR_NO_INPUT = -4;
43
44 // File not found which the service should process
45 const ERROR_FILE_NOT_FOUND = -20;
46
47 // File not readable
48 const ERROR_FILE_NOT_READABLE = -21;
49
50 // File not writable
51 // @todo: check writeable vs. writable
52 const ERROR_FILE_NOT_WRITEABLE = -22;
53
54 // Passed subtype is not possible with this service
55 const ERROR_PROGRAM_NOT_FOUND = -40;
56
57 // Passed subtype is not possible with this service
58 const ERROR_PROGRAM_FAILED = -41;
59 /**
60 * @var array service description array
61 */
62 public $info = [];
63
64 /**
65 * @var array error stack
66 */
67 public $error = [];
68
69 /**
70 * @var string The output content. That's what the services produced as result.
71 */
72 public $out = '';
73
74 /**
75 * @var string The file that should be processed.
76 */
77 public $inputFile = '';
78
79 /**
80 * @var string The content that should be processed.
81 */
82 public $inputContent = '';
83
84 /**
85 * @var string The type of the input content (or file). Might be the same as the service subtypes.
86 */
87 public $inputType = '';
88
89 /**
90 * @var string The file where the output should be written to.
91 */
92 public $outputFile = '';
93
94 /**
95 * Temporary files which have to be deleted
96 *
97 * @private
98 * @var array
99 */
100 public $tempFiles = [];
101
102 /**
103 * @var array list of registered shutdown functions; should be used to prevent registering the same function multiple times
104 */
105 protected $shutdownRegistry = [];
106
107 /**
108 * @var string Prefix for temporary files
109 */
110 protected $prefixId = '';
111
112 /***************************************
113 *
114 * Get service meta information
115 *
116 ***************************************/
117 /**
118 * Returns internal information array for service
119 *
120 * @return array Service description array
121 */
122 public function getServiceInfo()
123 {
124 return $this->info;
125 }
126
127 /**
128 * Returns the service key of the service
129 *
130 * @return string Service key
131 */
132 public function getServiceKey()
133 {
134 return $this->info['serviceKey'];
135 }
136
137 /**
138 * Returns the title of the service
139 *
140 * @return string Service title
141 */
142 public function getServiceTitle()
143 {
144 return $this->info['title'];
145 }
146
147 /**
148 * Returns service configuration values from the $TYPO3_CONF_VARS['SVCONF'] array
149 *
150 * @param string $optionName Name of the config option
151 * @param mixed $defaultValue Default configuration if no special config is available
152 * @param bool $includeDefaultConfig If set the 'default' config will be returned if no special config for this service is available (default: TRUE)
153 * @return mixed Configuration value for the service
154 */
155 public function getServiceOption($optionName, $defaultValue = '', $includeDefaultConfig = true)
156 {
157 $config = null;
158 $svOptions = $GLOBALS['TYPO3_CONF_VARS']['SVCONF'][$this->info['serviceType']];
159 if (isset($svOptions[$this->info['serviceKey']][$optionName])) {
160 $config = $svOptions[$this->info['serviceKey']][$optionName];
161 } elseif ($includeDefaultConfig && isset($svOptions['default'][$optionName])) {
162 $config = $svOptions['default'][$optionName];
163 }
164 if (!isset($config)) {
165 $config = $defaultValue;
166 }
167 return $config;
168 }
169
170 /***************************************
171 *
172 * Error handling
173 *
174 ***************************************/
175 /**
176 * Logs debug messages to the Logging API
177 *
178 * @param string $msg Debug message
179 * @param int $severity Severity: 0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
180 * @param array|bool $dataVar additional data you want to pass to the logger.
181 * @deprecated
182 */
183 public function devLog($msg, $severity = 0, $dataVar = false)
184 {
185 trigger_error('Method devLog() is deprecated since v9 and will be removed with v10', E_USER_DEPRECATED);
186 $this->logger->debug($this->info['serviceKey'] . ': ' . $msg, (array)$dataVar);
187 }
188
189 /**
190 * Puts an error on the error stack. Calling without parameter adds a general error.
191 *
192 * @param int $errNum Error number (see class constants)
193 * @param string $errMsg Error message
194 */
195 public function errorPush($errNum = self::ERROR_GENERAL, $errMsg = 'Unspecified error occurred')
196 {
197 $this->error[] = ['nr' => $errNum, 'msg' => $errMsg];
198 /** @var \TYPO3\CMS\Core\TimeTracker\TimeTracker $timeTracker */
199 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
200 $timeTracker->setTSlogMessage($errMsg, 2);
201 }
202
203 /**
204 * Removes the last error from the error stack.
205 */
206 public function errorPull()
207 {
208 array_pop($this->error);
209 }
210
211 /**
212 * Returns the last error number from the error stack.
213 *
214 * @return int|bool Error number (or TRUE if no error)
215 */
216 public function getLastError()
217 {
218 // Means all is ok - no error
219 $lastError = true;
220 if (!empty($this->error)) {
221 $error = end($this->error);
222 $lastError = $error['nr'];
223 }
224 return $lastError;
225 }
226
227 /**
228 * Returns the last message from the error stack.
229 *
230 * @return string Error message
231 */
232 public function getLastErrorMsg()
233 {
234 $lastErrorMessage = '';
235 if (!empty($this->error)) {
236 $error = end($this->error);
237 $lastErrorMessage = $error['msg'];
238 }
239 return $lastErrorMessage;
240 }
241
242 /**
243 * Returns all error messages as array.
244 *
245 * @return array Error messages
246 */
247 public function getErrorMsgArray()
248 {
249 $errArr = [];
250 if (!empty($this->error)) {
251 foreach ($this->error as $error) {
252 $errArr[] = $error['msg'];
253 }
254 }
255 return $errArr;
256 }
257
258 /**
259 * Returns the last array from the error stack.
260 *
261 * @return array Error number and message
262 */
263 public function getLastErrorArray()
264 {
265 return end($this->error);
266 }
267
268 /**
269 * Reset the error stack.
270 */
271 public function resetErrors()
272 {
273 $this->error = [];
274 }
275
276 /***************************************
277 *
278 * General service functions
279 *
280 ***************************************/
281 /**
282 * check the availability of external programs
283 *
284 * @param string $progList Comma list of programs 'perl,python,pdftotext'
285 * @return bool Return FALSE if one program was not found
286 */
287 public function checkExec($progList)
288 {
289 $ret = true;
290 $progList = GeneralUtility::trimExplode(',', $progList, true);
291 foreach ($progList as $prog) {
292 if (!CommandUtility::checkCommand($prog)) {
293 // Program not found
294 $this->errorPush(self::ERROR_PROGRAM_NOT_FOUND, 'External program not found: ' . $prog);
295 $ret = false;
296 }
297 }
298 return $ret;
299 }
300
301 /**
302 * Deactivate the service. Use this if the service fails at runtime and will not be available.
303 */
304 public function deactivateService()
305 {
306 ExtensionManagementUtility::deactivateService($this->info['serviceType'], $this->info['serviceKey']);
307 }
308
309 /***************************************
310 *
311 * IO tools
312 *
313 ***************************************/
314 /**
315 * Check if a file exists and is readable.
316 *
317 * @param string $absFile File name with absolute path.
318 * @return string|bool File name or FALSE.
319 */
320 public function checkInputFile($absFile)
321 {
322 $checkResult = false;
323 if (GeneralUtility::isAllowedAbsPath($absFile) && @is_file($absFile)) {
324 if (@is_readable($absFile)) {
325 $checkResult = $absFile;
326 } else {
327 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'File is not readable: ' . $absFile);
328 }
329 } else {
330 $this->errorPush(self::ERROR_FILE_NOT_FOUND, 'File not found: ' . $absFile);
331 }
332 return $checkResult;
333 }
334
335 /**
336 * Read content from a file a file.
337 *
338 * @param string $absFile File name to read from.
339 * @param int $length Maximum length to read. If empty the whole file will be read.
340 * @return string|bool $content or FALSE
341 */
342 public function readFile($absFile, $length = 0)
343 {
344 $out = false;
345 if ($this->checkInputFile($absFile)) {
346 $out = file_get_contents($absFile);
347 if ($out === false) {
348 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'Can not read from file: ' . $absFile);
349 }
350 }
351 return $out;
352 }
353
354 /**
355 * Write content to a file.
356 *
357 * @param string $content Content to write to the file
358 * @param string $absFile File name to write into. If empty a temp file will be created.
359 * @return string|bool File name or FALSE
360 */
361 public function writeFile($content, $absFile = '')
362 {
363 if (!$absFile) {
364 $absFile = $this->tempFile($this->prefixId);
365 }
366 if ($absFile && GeneralUtility::isAllowedAbsPath($absFile)) {
367 if ($fd = @fopen($absFile, 'wb')) {
368 @fwrite($fd, $content);
369 @fclose($fd);
370 } else {
371 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not write to file: ' . $absFile);
372 $absFile = false;
373 }
374 }
375 return $absFile;
376 }
377
378 /**
379 * Create a temporary file.
380 *
381 * @param string $filePrefix File prefix.
382 * @return string|bool File name or FALSE
383 */
384 public function tempFile($filePrefix)
385 {
386 $absFile = GeneralUtility::tempnam($filePrefix);
387 if ($absFile) {
388 $ret = $absFile;
389 $this->registerTempFile($absFile);
390 } else {
391 $ret = false;
392 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not create temp file.');
393 }
394 return $ret;
395 }
396
397 /**
398 * Register file which should be deleted afterwards.
399 *
400 * @param string $absFile File name with absolute path.
401 */
402 public function registerTempFile($absFile)
403 {
404 if (!isset($this->shutdownRegistry[__METHOD__])) {
405 register_shutdown_function([$this, 'unlinkTempFiles']);
406 $this->shutdownRegistry[__METHOD__] = true;
407 }
408 $this->tempFiles[] = $absFile;
409 }
410
411 /**
412 * Delete registered temporary files.
413 */
414 public function unlinkTempFiles()
415 {
416 foreach ($this->tempFiles as $absFile) {
417 GeneralUtility::unlink_tempfile($absFile);
418 }
419 $this->tempFiles = [];
420 }
421
422 /***************************************
423 *
424 * IO input
425 *
426 ***************************************/
427 /**
428 * Set the input content for service processing.
429 *
430 * @param mixed $content Input content (going into ->inputContent)
431 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
432 */
433 public function setInput($content, $type = '')
434 {
435 $this->inputContent = $content;
436 $this->inputFile = '';
437 $this->inputType = $type;
438 }
439
440 /**
441 * Set the input file name for service processing.
442 *
443 * @param string $absFile File name
444 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
445 */
446 public function setInputFile($absFile, $type = '')
447 {
448 $this->inputContent = '';
449 $this->inputFile = $absFile;
450 $this->inputType = $type;
451 }
452
453 /**
454 * Get the input content.
455 * Will be read from input file if needed. (That is if ->inputContent is empty and ->inputFile is not)
456 *
457 * @return mixed
458 */
459 public function getInput()
460 {
461 if ($this->inputContent == '') {
462 $this->inputContent = $this->readFile($this->inputFile);
463 }
464 return $this->inputContent;
465 }
466
467 /**
468 * Get the input file name.
469 * If the content was set by setContent a file will be created.
470 *
471 * @param string $createFile File name. If empty a temp file will be created.
472 * @return string File name or FALSE if no input or file error.
473 */
474 public function getInputFile($createFile = '')
475 {
476 if ($this->inputFile) {
477 $this->inputFile = $this->checkInputFile($this->inputFile);
478 } elseif ($this->inputContent) {
479 $this->inputFile = $this->writeFile($this->inputContent, $createFile);
480 }
481 return $this->inputFile;
482 }
483
484 /***************************************
485 *
486 * IO output
487 *
488 ***************************************/
489 /**
490 * Set the output file name.
491 *
492 * @param string $absFile File name
493 */
494 public function setOutputFile($absFile)
495 {
496 $this->outputFile = $absFile;
497 }
498
499 /**
500 * Get the output content.
501 *
502 * @return mixed
503 */
504 public function getOutput()
505 {
506 if ($this->outputFile) {
507 $this->out = $this->readFile($this->outputFile);
508 }
509 return $this->out;
510 }
511
512 /**
513 * Get the output file name. If no output file is set, the ->out buffer is written to the file given by input parameter filename
514 *
515 * @param string $absFile Absolute filename to write to
516 * @return mixed
517 */
518 public function getOutputFile($absFile = '')
519 {
520 if (!$this->outputFile) {
521 $this->outputFile = $this->writeFile($this->out, $absFile);
522 }
523 return $this->outputFile;
524 }
525
526 /***************************************
527 *
528 * Service implementation
529 *
530 ***************************************/
531 /**
532 * Initialization of the service.
533 *
534 * The class have to do a strict check if the service is available.
535 * example: check if the perl interpreter is available which is needed to run an extern perl script.
536 *
537 * @return bool TRUE if the service is available
538 */
539 public function init()
540 {
541 // look in makeInstanceService()
542 $this->reset();
543 // Check for external programs which are defined by $info['exec']
544 if (trim($this->info['exec'])) {
545 $this->checkExec($this->info['exec']);
546 }
547 return $this->getLastError() === true;
548 }
549
550 /**
551 * Resets the service.
552 * Will be called by init(). Should be used before every use if a service instance is used multiple times.
553 */
554 public function reset()
555 {
556 $this->unlinkTempFiles();
557 $this->resetErrors();
558 $this->out = '';
559 $this->inputFile = '';
560 $this->inputContent = '';
561 $this->inputType = '';
562 $this->outputFile = '';
563 }
564
565 /**
566 * Clean up the service.
567 * Child classes should explicitly call parent::__destruct() in their destructors for this to work
568 */
569 public function __destruct()
570 {
571 $this->unlinkTempFiles();
572 }
573 }