[BUGFIX] Streamline trigger_error messages
[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 $serviceType = $this->info['serviceType'] ?? '';
159 $serviceKey = $this->info['serviceKey'] ?? '';
160 $svOptions = $GLOBALS['TYPO3_CONF_VARS']['SVCONF'][$serviceType] ?? [];
161 if (isset($svOptions[$serviceKey][$optionName])) {
162 $config = $svOptions[$serviceKey][$optionName];
163 } elseif ($includeDefaultConfig && isset($svOptions['default'][$optionName])) {
164 $config = $svOptions['default'][$optionName];
165 }
166 if (!isset($config)) {
167 $config = $defaultValue;
168 }
169 return $config;
170 }
171
172 /***************************************
173 *
174 * Error handling
175 *
176 ***************************************/
177 /**
178 * Logs debug messages to the Logging API
179 *
180 * @param string $msg Debug message
181 * @param int $severity Severity: 0 is info, 1 is notice, 2 is warning, 3 is fatal error, -1 is "OK" message
182 * @param array|bool $dataVar additional data you want to pass to the logger.
183 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
184 */
185 public function devLog($msg, $severity = 0, $dataVar = false)
186 {
187 trigger_error('AbstractService->devLog() will be removed with TYPO3 v10.0.', E_USER_DEPRECATED);
188 $this->logger->debug($this->info['serviceKey'] . ': ' . $msg, (array)$dataVar);
189 }
190
191 /**
192 * Puts an error on the error stack. Calling without parameter adds a general error.
193 *
194 * @param int $errNum Error number (see class constants)
195 * @param string $errMsg Error message
196 */
197 public function errorPush($errNum = self::ERROR_GENERAL, $errMsg = 'Unspecified error occurred')
198 {
199 $this->error[] = ['nr' => $errNum, 'msg' => $errMsg];
200 /** @var \TYPO3\CMS\Core\TimeTracker\TimeTracker $timeTracker */
201 $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
202 $timeTracker->setTSlogMessage($errMsg, 2);
203 }
204
205 /**
206 * Removes the last error from the error stack.
207 */
208 public function errorPull()
209 {
210 array_pop($this->error);
211 }
212
213 /**
214 * Returns the last error number from the error stack.
215 *
216 * @return int|bool Error number (or TRUE if no error)
217 */
218 public function getLastError()
219 {
220 // Means all is ok - no error
221 $lastError = true;
222 if (!empty($this->error)) {
223 $error = end($this->error);
224 $lastError = $error['nr'];
225 }
226 return $lastError;
227 }
228
229 /**
230 * Returns the last message from the error stack.
231 *
232 * @return string Error message
233 */
234 public function getLastErrorMsg()
235 {
236 $lastErrorMessage = '';
237 if (!empty($this->error)) {
238 $error = end($this->error);
239 $lastErrorMessage = $error['msg'];
240 }
241 return $lastErrorMessage;
242 }
243
244 /**
245 * Returns all error messages as array.
246 *
247 * @return array Error messages
248 */
249 public function getErrorMsgArray()
250 {
251 $errArr = [];
252 if (!empty($this->error)) {
253 foreach ($this->error as $error) {
254 $errArr[] = $error['msg'];
255 }
256 }
257 return $errArr;
258 }
259
260 /**
261 * Returns the last array from the error stack.
262 *
263 * @return array Error number and message
264 */
265 public function getLastErrorArray()
266 {
267 return end($this->error);
268 }
269
270 /**
271 * Reset the error stack.
272 */
273 public function resetErrors()
274 {
275 $this->error = [];
276 }
277
278 /***************************************
279 *
280 * General service functions
281 *
282 ***************************************/
283 /**
284 * check the availability of external programs
285 *
286 * @param string $progList Comma list of programs 'perl,python,pdftotext'
287 * @return bool Return FALSE if one program was not found
288 */
289 public function checkExec($progList)
290 {
291 $ret = true;
292 $progList = GeneralUtility::trimExplode(',', $progList, true);
293 foreach ($progList as $prog) {
294 if (!CommandUtility::checkCommand($prog)) {
295 // Program not found
296 $this->errorPush(self::ERROR_PROGRAM_NOT_FOUND, 'External program not found: ' . $prog);
297 $ret = false;
298 }
299 }
300 return $ret;
301 }
302
303 /**
304 * Deactivate the service. Use this if the service fails at runtime and will not be available.
305 */
306 public function deactivateService()
307 {
308 ExtensionManagementUtility::deactivateService($this->info['serviceType'], $this->info['serviceKey']);
309 }
310
311 /***************************************
312 *
313 * IO tools
314 *
315 ***************************************/
316 /**
317 * Check if a file exists and is readable.
318 *
319 * @param string $absFile File name with absolute path.
320 * @return string|bool File name or FALSE.
321 */
322 public function checkInputFile($absFile)
323 {
324 $checkResult = false;
325 if (GeneralUtility::isAllowedAbsPath($absFile) && @is_file($absFile)) {
326 if (@is_readable($absFile)) {
327 $checkResult = $absFile;
328 } else {
329 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'File is not readable: ' . $absFile);
330 }
331 } else {
332 $this->errorPush(self::ERROR_FILE_NOT_FOUND, 'File not found: ' . $absFile);
333 }
334 return $checkResult;
335 }
336
337 /**
338 * Read content from a file a file.
339 *
340 * @param string $absFile File name to read from.
341 * @param int $length Maximum length to read. If empty the whole file will be read.
342 * @return string|bool $content or FALSE
343 */
344 public function readFile($absFile, $length = 0)
345 {
346 $out = false;
347 if ($this->checkInputFile($absFile)) {
348 $out = file_get_contents($absFile);
349 if ($out === false) {
350 $this->errorPush(self::ERROR_FILE_NOT_READABLE, 'Can not read from file: ' . $absFile);
351 }
352 }
353 return $out;
354 }
355
356 /**
357 * Write content to a file.
358 *
359 * @param string $content Content to write to the file
360 * @param string $absFile File name to write into. If empty a temp file will be created.
361 * @return string|bool File name or FALSE
362 */
363 public function writeFile($content, $absFile = '')
364 {
365 if (!$absFile) {
366 $absFile = $this->tempFile($this->prefixId);
367 }
368 if ($absFile && GeneralUtility::isAllowedAbsPath($absFile)) {
369 if ($fd = @fopen($absFile, 'wb')) {
370 @fwrite($fd, $content);
371 @fclose($fd);
372 } else {
373 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not write to file: ' . $absFile);
374 $absFile = false;
375 }
376 }
377 return $absFile;
378 }
379
380 /**
381 * Create a temporary file.
382 *
383 * @param string $filePrefix File prefix.
384 * @return string|bool File name or FALSE
385 */
386 public function tempFile($filePrefix)
387 {
388 $absFile = GeneralUtility::tempnam($filePrefix);
389 if ($absFile) {
390 $ret = $absFile;
391 $this->registerTempFile($absFile);
392 } else {
393 $ret = false;
394 $this->errorPush(self::ERROR_FILE_NOT_WRITEABLE, 'Can not create temp file.');
395 }
396 return $ret;
397 }
398
399 /**
400 * Register file which should be deleted afterwards.
401 *
402 * @param string $absFile File name with absolute path.
403 */
404 public function registerTempFile($absFile)
405 {
406 if (!isset($this->shutdownRegistry[__METHOD__])) {
407 register_shutdown_function([$this, 'unlinkTempFiles']);
408 $this->shutdownRegistry[__METHOD__] = true;
409 }
410 $this->tempFiles[] = $absFile;
411 }
412
413 /**
414 * Delete registered temporary files.
415 */
416 public function unlinkTempFiles()
417 {
418 foreach ($this->tempFiles as $absFile) {
419 GeneralUtility::unlink_tempfile($absFile);
420 }
421 $this->tempFiles = [];
422 }
423
424 /***************************************
425 *
426 * IO input
427 *
428 ***************************************/
429 /**
430 * Set the input content for service processing.
431 *
432 * @param mixed $content Input content (going into ->inputContent)
433 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
434 */
435 public function setInput($content, $type = '')
436 {
437 $this->inputContent = $content;
438 $this->inputFile = '';
439 $this->inputType = $type;
440 }
441
442 /**
443 * Set the input file name for service processing.
444 *
445 * @param string $absFile File name
446 * @param string $type The type of the input content (or file). Might be the same as the service subtypes.
447 */
448 public function setInputFile($absFile, $type = '')
449 {
450 $this->inputContent = '';
451 $this->inputFile = $absFile;
452 $this->inputType = $type;
453 }
454
455 /**
456 * Get the input content.
457 * Will be read from input file if needed. (That is if ->inputContent is empty and ->inputFile is not)
458 *
459 * @return mixed
460 */
461 public function getInput()
462 {
463 if ($this->inputContent == '') {
464 $this->inputContent = $this->readFile($this->inputFile);
465 }
466 return $this->inputContent;
467 }
468
469 /**
470 * Get the input file name.
471 * If the content was set by setContent a file will be created.
472 *
473 * @param string $createFile File name. If empty a temp file will be created.
474 * @return string File name or FALSE if no input or file error.
475 */
476 public function getInputFile($createFile = '')
477 {
478 if ($this->inputFile) {
479 $this->inputFile = $this->checkInputFile($this->inputFile);
480 } elseif ($this->inputContent) {
481 $this->inputFile = $this->writeFile($this->inputContent, $createFile);
482 }
483 return $this->inputFile;
484 }
485
486 /***************************************
487 *
488 * IO output
489 *
490 ***************************************/
491 /**
492 * Set the output file name.
493 *
494 * @param string $absFile File name
495 */
496 public function setOutputFile($absFile)
497 {
498 $this->outputFile = $absFile;
499 }
500
501 /**
502 * Get the output content.
503 *
504 * @return mixed
505 */
506 public function getOutput()
507 {
508 if ($this->outputFile) {
509 $this->out = $this->readFile($this->outputFile);
510 }
511 return $this->out;
512 }
513
514 /**
515 * Get the output file name. If no output file is set, the ->out buffer is written to the file given by input parameter filename
516 *
517 * @param string $absFile Absolute filename to write to
518 * @return mixed
519 */
520 public function getOutputFile($absFile = '')
521 {
522 if (!$this->outputFile) {
523 $this->outputFile = $this->writeFile($this->out, $absFile);
524 }
525 return $this->outputFile;
526 }
527
528 /***************************************
529 *
530 * Service implementation
531 *
532 ***************************************/
533 /**
534 * Initialization of the service.
535 *
536 * The class have to do a strict check if the service is available.
537 * example: check if the perl interpreter is available which is needed to run an extern perl script.
538 *
539 * @return bool TRUE if the service is available
540 */
541 public function init()
542 {
543 // look in makeInstanceService()
544 $this->reset();
545 // Check for external programs which are defined by $info['exec']
546 if (trim($this->info['exec'])) {
547 $this->checkExec($this->info['exec']);
548 }
549 return $this->getLastError() === true;
550 }
551
552 /**
553 * Resets the service.
554 * Will be called by init(). Should be used before every use if a service instance is used multiple times.
555 */
556 public function reset()
557 {
558 $this->unlinkTempFiles();
559 $this->resetErrors();
560 $this->out = '';
561 $this->inputFile = '';
562 $this->inputContent = '';
563 $this->inputType = '';
564 $this->outputFile = '';
565 }
566
567 /**
568 * Clean up the service.
569 * Child classes should explicitly call parent::__destruct() in their destructors for this to work
570 */
571 public function __destruct()
572 {
573 $this->unlinkTempFiles();
574 }
575 }